# 初探ERC3525

By [Omen](https://paragraph.com/@omen) · 2023-02-15

---

ERC3525
=======

半同质化(semi-fungible)标准, ERC3525定义一个`<Id，Slot，Value>`三重参数模型，用于表示Token的半同质化结构。

基于`Id`参数,保证了兼容ERC721同质化的特性,可以通过`transfer`在地址间转移,也可以实现`aprrove`方法;

`Value`参数,类似ERC20标准的Token数量`balance`;

`Slot`参数, 当两个不同ID的Token具有相同的`Slot`时,可视这两个Token是同质化的. 即通过SLOT可以判断不同ID间的Token是否为同质化的.

TL;DR
-----

ERC3525标准在非同质化ERC721通证的基础之上,

1.  通过引入`Slot`参数, 实现了TokenId级别的半同质化特性: 不同TokenId的Slot参数相同,则同质; 若不同,则非同质.
    
2.  引入同质化特性后, 即可加入`Value`参数, 标识单个TokenId内有多少数量(value)的Token( or 任何有含义的资产).
    
3.  ERC3525标准则在`Slot`和`Value`维度上, 扩展了类似ERC721的授权,转移和查询方法,具体见文.
    
4.  此外, ERC3525标准建议将metadata数据存于链上, 可以通过`Slot`来在链上索引Slot维度的metadata数据, 也可以实现TokenId维度的metadata获取.
    

1 基本ERC3525标准
-------------

继承自IERC165和IERC721标准`interface IERC3525 /* is IERC165, IERC721 */ {...}`.对比ERC721,以下从授权,转移和查询(approve, transfer and view)三个维度对ERC3525方法进行分类:

### 1.1 关于授权(approve)

    // 721方法, msg.sender授权_tokenId的操作权限给_approved地址
    function approve(address _approved, uint256 _tokenId) external payable;
    // 721方法, msg.sender授权(或取消授权)其所有权限给_operator地址
    function setApprovalForAll(address _operator, bool _approved) external;
    // 721方法, 查询单个_tokenId的授权情况, 针对approve(address,uint256)方法
    function getApproved(uint256 _tokenId) external view returns (address);
    // 721方法, 查询拥有者_owner地址的授权情况,针对setApprovalForAll方法
    function isApprovedForAll(address _owner, address _operator) external view returns (bool);
    
    // 3525方法, msg.sender授权_tokenId的_value数量的操作权限给_operator地址
    function approve(uint256 _tokenId, address _operator, uint256 _value) external payable;
    // 3525方法, 查询_operator操作地址对单个_tokenId的授权情况, 针对approve(uint256,address,uint256)方法
    function allowance(uint256 _tokenId, address _operator) external view returns (uint256);
    

三个授权层级:

*   (ERC721)针对单个地址的所有操作权限: setApprovalForAll
    
*   (ERC721)针对单个TokenId的所有操作权限: approve(address, uint256)
    
*   (ERC3525)针对单个TokenId内指定Value数量的操作权限: approve(uint256, address, uint256)
    

根据以上三类授权层级, 分别有相应的查询方法. 在本文2.2部分可见, 扩展的IERC3525SlotApprovable标准还增加了Slot级别的授权层级;

### 1.2 关于转移(transfer)

    // 721方法, 将_tokenId从_from地址转移至_to地址;附加校验_to地址是否为合约地址; 附加额外数据data;
    function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;
    // 721方法, 将_tokenId从_from地址转移至_to地址;附加额外校验, 一般为onERC721Received;
    function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;
    // 721方法, 将_tokenId从_from地址转移至_to地址;
    function transferFrom(address _from, address _to, uint256 _tokenId) external payable;
    
    // 3525方法, 将单个_fromTokenId中_value数量转移至单个_toTokenId中; 要求_fromTokenId和_toTokenId是同质的, 即slot参数要相同;
    function transferFrom(uint256 _fromTokenId, uint256 _toTokenId, uint256 _value) external payable;
    // 3525方法, 将单个_fromTokenId中_value数量转移给_to地址; 若_to存在slot与_fromTokenId的tokenId, 则累加, 否则需要新创建一个tokenId;
    function transferFrom(uint256 _fromTokenId, address _to, uint256 _value) external payable returns (uint256);
    

两个转移层级:

*   (ERC721)针对单个TokenId的转移
    
*   (ERC3525)针对单个TokenId下Value的转移
    
    *   同质化转移: 相同`Slot`的token之间的`Value`值转移
        
    *   非同质化转移: 从Token到地址的`Value`值转移
        

### 1.3 关于查询(view)

如图, 相比ERC721包含的查询方法1和2,ERC3525增加了针对TokenId下的Value以及Slot的查询方法.

ERC3525查询

    // 721方法, 查询_owner拥有TokenId的数量, 对应箭头1
    function balanceOf(address _owner) external view returns (uint256);    
    // 721方法, 查询_tokenId的拥有者, 对应箭头2
    function ownerOf(uint256 _tokenId) external view returns (address);
    
    // 3525方法, 查询单个_tokenId目前拥有的Value数量, 对应箭头3
    function balanceOf(uint256 _tokenId) external view returns (uint256);
    // 3525方法, 查询单个_tokenId对应的slot参数, 用于区分不同TokenId之间是否为同质的, 对应箭头4
    function slotOf(uint256 _tokenId) external view returns (uint256);
    // 3525方法, 查询Value值的精度数(小数位数), 对应箭头5
    function valueDecimals() external view returns (uint8);
    

2 扩展标准
------

### 2.1 扩展slot枚举标准(IERC3525SlotEnumerable)

继承自IERC3525和IERC721Enumerable: `interface IERC3525SlotEnumerable is IERC3525 /* , IERC721Enumerable */ {...}`. 在枚举TokenId的基础之上,实现了针对Slot的枚举方法.

该标准的ERC165标识符为:`0x3b741b9e`

    // 721Enumerable方法, 获取TokenId的总供应量
    function totalSupply() external view returns (uint256);
    // 721Enumerable方法, 枚举获取TokenId
    function tokenByIndex(uint256 _index) external view returns (uint256);
    // 721Enumerable方法, 枚举获取Owner地址拥有的TokenId
    function tokenOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256);
    
    // 3525SlotEnumerable方法, 获取Slot的总数, 类比totalSupply()
    function slotCount() external view returns (uint256);
    // 3525SlotEnumerable方法, 枚举获取Slot, 类比tokenByIndex(uint256)
    function slotByIndex(uint256 _index) external view returns (uint256);
    // 3525SlotEnumerable方法, 获取指定Slot的总数, (~~类比totalSupplyInOwner(address _owner) returns(uint256 amount), hhhhh~~)
    function tokenSupplyInSlot(uint256 _slot) external view returns (uint256);
    // 3525SlotEnumerable方法, 枚举指定Slot, 获取TokenId, 类比tokenOfOwnerByIndex(address, uint256)
    function tokenInSlotByIndex(uint256 _slot, uint256 _index) external view returns (uint256);
    

### 2.2 扩展slot授权标准(IERC3525SlotApprovable)

继承自IERC3525,实现对slot维度的授权

该标准的ERC165标识符为:`0xb688be58`

    // 3525SlotApprovable方法, _owner授权(或取消授权)_slot的的操作权限给_operator地址
    function setApprovalForSlot(address _owner, uint256 _slot, address _operator, bool _approved) external payable;
    // 3525SlotApprovable, 查询_operator对_owner拥有的_slot是否拥有操作权限
    function isApprovedForSlot(address _owner, uint256 _slot, address _operator) external view returns (bool);
    

### 2.3 扩展元数据标准(IERC3525Metadata)

继承自IERC3525和IERC721Metadata: `interface IERC3525Metadata is IERC3525 /* , IERC721Metadata */ {...}`.

该标准的ERC165标识符为:`0xe1600902`

    // 721Metadata方法, 该系列Token的名字
    function name() external view returns (string _name);
    // 721Metadata方法, 该系列Token的符号
    function symbol() external view returns (string _symbol);
    // 721Metadata方法, 指定TokenId的元数据: 可以是外部url, 也可以是直接由链上数据生成; 
    //                  标准建议返回以’data:application/json;‘起始的json格式数据;
    function tokenURI(uint256 _tokenId) external view returns (string);
    
    // 3525Metadata方法, 该系列Token整体的信息: 标准建议返回以’data:application/json;‘起始的json格式数据;
    function contractURI() external view returns (string memory);
    // 3525Metadata方法, 指定slot的元数据: 标准建议返回以’data:application/json;‘起始的json格式数据;
    function slotURI(uint256 _slot) external view returns (string memory);
    

关于contract, token和slot维度的metadata数据, 标准建议返回以\`data:application/json;‘起始的json格式数据, 示例如下:

    function slotURI(uint256 slot_)
        public
        view
        virtual
        override
        returns (string memory)
    {
        return
            string(
                abi.encodePacked(
                    // solhint-disable 
                    "data:application/json;base64,",
                    Base64.encode(
                        abi.encodePacked(
                            '{"name":"',
                            _slotDetails[slot_].name,
                            '","description":"',
                            _slotDetails[slot_].description,
                            '","image":"',
                            _slotDetails[slot_].image,
                            '","properties":',
                            _slotProperties(slot_),
                            "}"
                        )
                    )
                    // solhint-enable
                )
            );
    }
    

3 合约接收标准(Token Receiver)
------------------------

该标准的ERC165标识符为:`0x009ce20b`

    interface IERC3525Receiver {
        // @return bytes4(keccak256('onERC3525Received(address,uint256,uint256,uint256,bytes)')) == 0x009ce20b
        function onERC3525Received(address _operator, uint256 _fromTokenId, uint256 _toTokenId, uint256 _value, bytes calldata _data) external returns (bytes4);
    }

---

*Originally published on [Omen](https://paragraph.com/@omen/erc3525)*
