初探ERC3525

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标准则在SlotValue维度上, 扩展了类似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);
}