---eip:3525title:半同质化代币description:定义了一种规范,其中具有相同 SLOT 和不同 ID 的 ERC-721 兼容代币是可互换的。author:Will Wang (@will42w), Mike Meng <myan@solv.finance>, Yi Cai (@YeeTsai) <yee.tsai@gmail.com>, Ryan Chow <ryanchow@solv.finance>, Zhongxin Wu (@Nerverwind), AlvisDu (@AlvisDu)discussions-to:https://ethereum-magicians.org/t/eip-3525-the-semi-fungible-tokenstatus:Finaltype:Standards Trackcategory:ERCcreated:2020-12-01requires:20, 165, 721---## 摘要
这是一个关于半同质化代币的标准。本文档中描述的智能合约接口集定义了一个 [ERC-721](/docs/eips/EIPS/eip-721/) 兼容的代币标准。该标准引入了一个 `<ID, SLOT, VALUE>` 三元标量模型,该模型表示代币的半同质化结构。它还引入了新的转移模型以及反映代币半同质化性质的授权模型。
代币包含一个等效于 ERC-721 的 ID 属性,以将其标识为普遍唯一的实体,以便代币可以在地址之间转移,并被授权以 ERC-721 兼容的方式进行操作。
代币还包含一个 `value` 属性,表示代币的定量性质。`value` 属性的含义与 [ERC-20](/docs/eips/EIPS/eip-20/) 代币的 `balance` 属性非常相似。每个代币都有一个 `slot` 属性,确保将具有相同 slot 的两个代币的值视为可互换的,从而为代币的 value 属性增加同质性。
此 EIP 引入了用于半同质化的新代币转移模型,包括同一 slot 的两个代币之间的 value 转移以及从代币到地址的 value 转移。
## 动机
代币化是使用和控制加密数字资产的最重要趋势之一。传统上,有两种方法可以做到这一点:同质化和非同质化代币。同质化代币通常使用 ERC-20 标准,其中每个单位的资产彼此相同。ERC-20 是一种灵活高效的操作同质化代币的方式。非同质化代币主要为 ERC-721 代币,这是一种能够根据身份区分数字资产的标准。
但是,两者都有明显的缺点。例如,ERC-20 要求用户为每个单独的数据结构或可自定义属性的组合创建一个单独的 ERC-20 合约。实际上,这导致需要创建大量的 ERC-20 合约。另一方面,ERC-721 代币不提供定量功能,从而大大削弱了它们的可计算性、流动性和可管理性。例如,如果有人要使用 ERC-721 创建金融工具(如债券、保险单或归属计划),则没有标准接口可供我们控制其中的 value,从而无法转移代币所代表的合约中的一部分权益。
解决此问题的一种更直观、更直接的方法是创建一个同时具有 ERC-20 的定量功能和 ERC-721 的定性属性的半同质化代币。这种半同质化代币与 ERC-721 的向后兼容性将有助于利用已在使用的现有基础设施,并加快采用速度。
## 规范
本文档中使用的关键词“MUST”、“MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、“MAY”和“OPTIONAL”应按照 [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt) 中的描述进行解释。
**每个 [ERC-3525](/docs/eips/EIPS/eip-3525/) 兼容合约必须实现 ERC-3525、ERC-721 和 [ERC-165](/docs/eips/EIPS/eip-165/) 接口**```solidity
pragmasolidity^0.8.0;/**
* @title ERC-3525 Semi-Fungible Token Standard
* Note: the ERC-165 identifier for this interface is 0xd5358140.
*/interfaceIERC3525/* is IERC165, IERC721 */{/**
* @dev MUST emit when value of a token is transferred to another token with the same slot,
* including zero value transfers (_value == 0) as well as transfers when tokens are created
* (`_fromTokenId` == 0) or destroyed (`_toTokenId` == 0).
* 当一个代币的 value 被转移到另一个具有相同 slot 的代币时,必须发出此事件
* 包括零 value 转移 (_value == 0) 以及创建代币时 (`_fromTokenId` == 0) 或销毁代币时 (`_toTokenId` == 0) 的转移。
* @param _fromTokenId The token id to transfer value from
* @param _toTokenId The token id to transfer value to
* @param _value The transferred value
*/eventTransferValue(uint256indexed_fromTokenId,uint256indexed_toTokenId,uint256_value);/**
* @dev MUST emit when the approval value of a token is set or changed.
* 当一个代币的授权 value 被设置或更改时,必须发出此事件。
* @param _tokenId The token to approve
* @param _operator The operator to approve for
* @param _value The maximum value that `_operator` is allowed to manage
*/eventApprovalValue(uint256indexed_tokenId,addressindexed_operator,uint256_value);/**
* @dev MUST emit when the slot of a token is set or changed.
* 当一个代币的 slot 被设置或更改时,必须发出此事件。
* @param _tokenId The token of which slot is set or changed
* @param _oldSlot The previous slot of the token
* @param _newSlot The updated slot of the token
*/eventSlotChanged(uint256indexed_tokenId,uint256indexed_oldSlot,uint256indexed_newSlot);/**
* @notice Get the number of decimals the token uses for value - e.g. 6, means the user
* representation of the value of a token can be calculated by dividing it by 1,000,000.
* 获取代币用于 value 的小数位数 - 例如,6 表示可以通过将代币 value 除以 1,000,000 来计算代币 value 的用户表示形式。
* Considering the compatibility with third-party wallets, this function is defined as
* `valueDecimals()` instead of `decimals()` to avoid conflict with ERC-20 tokens.
* 考虑到与第三方钱包的兼容性,此函数定义为 `valueDecimals()` 而不是 `decimals()`,以避免与 ERC-20 代币冲突。
* @return The number of decimals for value
*/functionvalueDecimals()externalviewreturns(uint8);/**
* @notice Get the value of a token.
* 获取代币的 value。
* @param _tokenId The token for which to query the balance
* @return The value of `_tokenId`
*/functionbalanceOf(uint256_tokenId)externalviewreturns(uint256);/**
* @notice Get the slot of a token.
* 获取代币的 slot。
* @param _tokenId The identifier for a token
* @return The slot of the token
*/functionslotOf(uint256_tokenId)externalviewreturns(uint256);/**
* @notice Allow an operator to manage the value of a token, up to the `_value`.
* 允许 operator 管理代币的 value,最高可达 `_value`。
* @dev MUST revert unless caller is the current owner, an authorized operator, or the approved
* address for `_tokenId`.
* 除非调用者是当前所有者、授权的 operator 或 `_tokenId` 的授权地址,否则必须回退。
* MUST emit the ApprovalValue event.
* 必须发出 ApprovalValue 事件。
* @param _tokenId The token to approve
* @param _operator The operator to be approved
* @param _value The maximum value of `_toTokenId` that `_operator` is allowed to manage
*/functionapprove(uint256_tokenId,address_operator,uint256_value)externalpayable;/**
* @notice Get the maximum value of a token that an operator is allowed to manage.
* 获取 operator 允许管理的一个代币的最大 value。
* @param _tokenId The token for which to query the allowance
* @param _operator The address of an operator
* @return The current approval value of `_tokenId` that `_operator` is allowed to manage
*/functionallowance(uint256_tokenId,address_operator)externalviewreturns(uint256);/**
* @notice Transfer value from a specified token to another specified token with the same slot.
* 将 value 从指定的代币转移到另一个具有相同 slot 的指定代币。
* @dev Caller MUST be the current owner, an authorized operator or an operator who has been
* approved the whole `_fromTokenId` or part of it.
* 调用者必须是 `_fromTokenId` 的当前所有者、授权的 operator 或已被授权全部 `_fromTokenId` 或其中一部分的 operator。
* MUST revert if `_fromTokenId` or `_toTokenId` is zero token id or does not exist.
* 如果 `_fromTokenId` 或 `_toTokenId` 为零代币 id 或不存在,则必须回退。
* MUST revert if slots of `_fromTokenId` and `_toTokenId` do not match.
* 如果 `_fromTokenId` 和 `_toTokenId` 的 slot 不匹配,则必须回退。
* MUST revert if `_value` exceeds the balance of `_fromTokenId` or its allowance to the
* operator.
* 如果 `_value` 超过 `_fromTokenId` 的余额或其对 operator 的授权,则必须回退。
* MUST emit `TransferValue` event.
* 必须发出 `TransferValue` 事件。
* @param _fromTokenId The token to transfer value from
* @param _toTokenId The token to transfer value to
* @param _value The transferred value
*/functiontransferFrom(uint256_fromTokenId,uint256_toTokenId,uint256_value)externalpayable;/**
* @notice Transfer value from a specified token to an address. The caller should confirm that
* `_to` is capable of receiving ERC-3525 tokens.
* 将 value 从指定的代币转移到一个地址。调用者应确认 `_to` 能够接收 ERC-3525 代币。
* @dev This function MUST create a new ERC-3525 token with the same slot for `_to`,
* or find an existing token with the same slot owned by `_to`, to receive the transferred value.
* 此函数必须为 `_to` 创建一个具有相同 slot 的新 ERC-3525 代币,或者找到一个 `_to` 拥有的具有相同 slot 的现有代币,以接收转移的 value。
* MUST revert if `_fromTokenId` is zero token id or does not exist.
* 如果 `_fromTokenId` 为零代币 id 或不存在,则必须回退。
* MUST revert if `_to` is zero address.
* 如果 `_to` 为零地址,则必须回退。
* MUST revert if `_value` exceeds the balance of `_fromTokenId` or its allowance to the
* operator.
* 如果 `_value` 超过 `_fromTokenId` 的余额或其对 operator 的授权,则必须回退。
* MUST emit `Transfer` and `TransferValue` events.
* 必须发出 `Transfer` 和 `TransferValue` 事件。
* @param _fromTokenId The token to transfer value from
* @param _to The address to transfer value to
* @param _value The transferred value
* @return ID of the token which receives the transferred value
*/functiontransferFrom(uint256_fromTokenId,address_to,uint256_value)externalpayablereturns(uint256);}
pragmasolidity^0.8.0;/**
* @title ERC-3525 Semi-Fungible Token Standard, optional extension for slot enumeration
* @dev Interfaces for any contract that wants to support enumeration of slots as well as tokens
* with the same slot.
* 用于任何想要支持枚举 slot 以及具有相同 slot 的代币的合约的接口。
* Note: the ERC-165 identifier for this interface is 0x3b741b9e.
*/interfaceIERC3525SlotEnumerableisIERC3525/* , IERC721Enumerable */{/**
* @notice Get the total amount of slots stored by the contract.
* 获取合约存储的 slot 总数。
* @return The total amount of slots
*/functionslotCount()externalviewreturns(uint256);/**
* @notice Get the slot at the specified index of all slots stored by the contract.
* 获取合约存储的所有 slot 中指定索引处的 slot。
* @param _index The index in the slot list
* @return The slot at `index` of all slots.
*/functionslotByIndex(uint256_index)externalviewreturns(uint256);/**
* @notice Get the total amount of tokens with the same slot.
* 获取具有相同 slot 的代币总数。
* @param _slot The slot to query token supply for
* @return The total amount of tokens with the specified `_slot`
*/functiontokenSupplyInSlot(uint256_slot)externalviewreturns(uint256);/**
* @notice Get the token at the specified index of all tokens with the same slot.
* 获取具有相同 slot 的所有代币中指定索引处的代币。
* @param _slot The slot to query tokens with
* @param _index The index in the token list of the slot
* @return The token ID at `_index` of all tokens with `_slot`
*/functiontokenInSlotByIndex(uint256_slot,uint256_index)externalviewreturns(uint256);}
pragmasolidity^0.8.0;/**
* @title ERC-3525 Semi-Fungible Token Standard, optional extension for approval of slot level
* @dev Interfaces for any contract that wants to support approval of slot level, which allows an
* operator to manage one's tokens with the same slot.
* 用于任何想要支持 slot 级别授权的合约的接口,该授权允许 operator 管理一个人具有相同 slot 的代币。
* See https://eips.ethereum.org/EIPS/eip-3525
* Note: the ERC-165 identifier for this interface is 0xb688be58.
*/interfaceIERC3525SlotApprovableisIERC3525{/**
* @dev MUST emit when an operator is approved or disapproved to manage all of `_owner`'s
* tokens with the same slot.
* 当批准或不批准 operator 管理所有 `_owner` 的具有相同 slot 的代币时,必须发出此事件。
* @param _owner The address whose tokens are approved
* @param _slot The slot to approve, all of `_owner`'s tokens with this slot are approved
* @param _operator The operator being approved or disapproved
* @param _approved Identify if `_operator` is approved or disapproved
*/eventApprovalForSlot(addressindexed_owner,uint256indexed_slot,addressindexed_operator,bool_approved);/**
* @notice Approve or disapprove an operator to manage all of `_owner`'s tokens with the
* specified slot.
* 批准或不批准 operator 管理所有 `_owner` 的具有指定 slot 的代币。
* @dev Caller SHOULD be `_owner` or an operator who has been authorized through
* `setApprovalForAll`.
* 调用者应该是 `_owner` 或已通过 `setApprovalForAll` 授权的 operator。
* MUST emit ApprovalSlot event.
* 必须发出 ApprovalSlot 事件。
* @param _owner The address that owns the ERC-3525 tokens
* @param _slot The slot of tokens being queried approval of
* @param _operator The address for whom to query approval
* @param _approved Identify if `_operator` would be approved or disapproved
*/functionsetApprovalForSlot(address_owner,uint256_slot,address_operator,bool_approved)externalpayable;/**
* @notice Query if `_operator` is authorized to manage all of `_owner`'s tokens with the
* specified slot.
* 查询 `_operator` 是否被授权管理所有 `_owner` 的具有指定 slot 的代币。
* @param _owner The address that owns the ERC-3525 tokens
* @param _slot The slot of tokens being queried approval of
* @param _operator The address for whom to query approval
* @return True if `_operator` is authorized to manage all of `_owner`'s tokens with `_slot`,
* false otherwise.
* 如果 `_operator` 被授权管理所有 `_owner` 的具有 `_slot` 的代币,则为 True,否则为 False。
*/functionisApprovedForSlot(address_owner,uint256_slot,address_operator)externalviewreturns(bool);}
ERC-3525 代币接收器
如果一个智能合约想要在接收到来自其他地址的 value 时得到通知,它应该实现 IERC3525Receiver 接口中的所有函数,在实现中它可以决定是否接受或拒绝转移。有关更多详细信息,请参阅“转移规则”。
pragmasolidity^0.8.0;/**
* @title ERC-3525 token receiver interface
* @dev Interface for a smart contract that wants to be informed by ERC-3525 contracts when receiving values from ANY addresses or ERC-3525 tokens.
* 用于智能合约的接口,该智能合约希望在接收到来自任何地址或 ERC-3525 代币的 value 时得到 ERC-3525 合约的通知。
* Note: the ERC-165 identifier for this interface is 0x009ce20b.
*/interfaceIERC3525Receiver{/**
* @notice Handle the receipt of an ERC-3525 token value.
* 处理接收 ERC-3525 代币的 value。
* @dev An ERC-3525 smart contract MUST check whether this function is implemented by the recipient contract, if the
* recipient contract implements this function, the ERC-3525 contract MUST call this function after a
* value transfer (i.e. `transferFrom(uint256,uint256,uint256,bytes)`).
* ERC-3525 智能合约必须检查接收者合约是否实现了此函数,如果接收者合约实现了此函数,则 ERC-3525 合约必须在 value 转移后调用此函数(即 `transferFrom(uint256,uint256,uint256,bytes)`)。
* MUST return 0x009ce20b (i.e. `bytes4(keccak256('onERC3525Received(address,uint256,uint256,
* uint256,bytes)'))`) if the transfer is accepted.
* 如果转移被接受,必须返回 0x009ce20b(即 `bytes4(keccak256('onERC3525Received(address,uint256,uint256,uint256,bytes)'))`)。
* MUST revert or return any value other than 0x009ce20b if the transfer is rejected.
* 如果转移被拒绝,必须回退或返回除 0x009ce20b 之外的任何 value。
* @param _operator The address which triggered the transfer
* @param _fromTokenId The token id to transfer value from
* @param _toTokenId The token id to transfer value to
* @param _value The transferred value
* @param _data Additional data with no specified format
* @return `bytes4(keccak256('onERC3525Received(address,uint256,uint256,uint256,bytes)'))`
* unless the transfer is rejected.
* 除非转移被拒绝,否则返回 `bytes4(keccak256('onERC3525Received(address,uint256,uint256,uint256,bytes)'))`。
*/functiononERC3525Received(address_operator,uint256_fromTokenId,uint256_toTokenId,uint256_value,bytescalldata_data)externalreturns(bytes4);}
代币操作
场景
转移:
除了 ERC-721 兼容的代币转移方法外,此 EIP 还引入了两个新的转移模型:从 ID 到 ID 的 value 转移,以及从 ID 到地址的 value 转移。
如果 _value 超过 _fromTokenId 的 value 或其对 operator 的授权,则必须回退。
如果 _to 地址是一个智能合约,则必须检查 onERC3525Received 函数,如果该函数存在,则必须在 value 转移后调用此函数,如果结果不等于 0x009ce20b,则必须回退;
必须发出 Transfer 和 TransferValue 事件。
元数据
元数据扩展
ERC-3525 元数据扩展与 ERC-721 元数据扩展兼容。
此可选接口可以通过 ERC-165 标准接口检测来识别。
pragmasolidity^0.8.0;/**
* @title ERC-3525 Semi-Fungible Token Standard, optional extension for metadata
* @dev Interfaces for any contract that wants to support query of the Uniform Resource Identifier
* (URI) for the ERC-3525 contract as well as a specified slot.
* 用于任何想要支持查询 ERC-3525 合约以及指定 slot 的统一资源标识符 (URI) 的合约的接口。
* Because of the higher reliability of data stored in smart contracts compared to data stored in
* centralized systems, it is recommended that metadata, including `contractURI`, `slotURI` and
* `tokenURI`, be directly returned in JSON format, instead of being returned with a url pointing
* to any resource stored in a centralized system.
* 由于与存储在集中式系统中的数据相比,存储在智能合约中的数据具有更高的可靠性,因此建议以 JSON 格式直接返回元数据,包括 `contractURI`、`slotURI` 和 `tokenURI`,而不是返回指向存储在集中式系统中的任何资源的 URL。
* See https://eips.ethereum.org/EIPS/eip-3525
* Note: the ERC-165 identifier for this interface is 0xe1600902.
*/interfaceIERC3525MetadataisIERC3525/* , IERC721Metadata */{/**
* @notice Returns the Uniform Resource Identifier (URI) for the current ERC-3525 contract.
* 返回当前 ERC-3525 合约的统一资源标识符 (URI)。
* @dev This function SHOULD return the URI for this contract in JSON format, starting with
* header `data:application/json;`.
* 此函数应该以 JSON 格式返回此合约的 URI,以 `data:application/json;` 标头开头。
* See https://eips.ethereum.org/EIPS/eip-3525 for the JSON schema for contract URI.
* @return The JSON formatted URI of the current ERC-3525 contract
*/functioncontractURI()externalviewreturns(stringmemory);/**
* @notice Returns the Uniform Resource Identifier (URI) for the specified slot.
* 返回指定 slot 的统一资源标识符 (URI)。
* @dev This function SHOULD return the URI for `_slot` in JSON format, starting with header
* `data:application/json;`.
* 此函数应该以 JSON 格式返回 `_slot` 的 URI,以 `data:application/json;` 标头开头。
* See https://eips.ethereum.org/EIPS/eip-3525 for the JSON schema for slot URI.
* @return The JSON formatted URI of `_slot`
*/functionslotURI(uint256_slot)externalviewreturns(stringmemory);}