TRC-1155 协议接口

TRC-1155 标准中每个函数和事件的完整参考,以及接收者和元数据扩展。

📘

前置阅读

本文是 TRC-1155 的函数与事件参考。TRC-1155 允许单个合约同时管理同质化、非同质化、半同质化 Token,并支持批量转账与授权。

📘

ERC-1155 兼容性

TRC-1155 使用与 ERC-1155 完全一致的选择器:onERC1155Received 对应 0xf23a6e61onERC1155BatchReceived 对应 0xbc197c81。ERC-1155 合约可直接移植到 TRON,无需修改接收者钩子返回值——与 TRC-721 不同,后者使用 TRON 特有的哈希 0x5175f878

必须实现的接口

TRC-1155 合约必须实现 TRC-1155TRC-165 接口:

interface ITRC1155 {
    // 事件
    event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);
    event TransferSingle(address indexed _operator, address indexed _from, address indexed _to, uint256 _id, uint256 _value);
    event TransferBatch(address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _values);
    event URI(string _value, uint256 indexed _id);

    // 必须实现的函数
    function setApprovalForAll(address _operator, bool _approved) external;
    function isApprovedForAll(address _owner, address _operator) external view returns (bool);
    function balanceOf(address _owner, uint256 _id) external view returns (uint256);
    function balanceOfBatch(address[] calldata _owners, uint256[] calldata _ids) external view returns (uint256[] memory);
    function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data) external;
    function safeBatchTransferFrom(address _from, address _to, uint256[] calldata _ids, uint256[] calldata _values, bytes calldata _data) external;
}

interface ITRC165 {
    function supportsInterface(bytes4 interfaceID) external view returns (bool);
}

函数参考

函数用途
setApprovalForAll(address _operator, bool _approved)授权或撤销 _operator 代表调用方转账该合约下所有 Token 的权限。触发 ApprovalForAll 事件。
isApprovedForAll(address _owner, address _operator)_operator 当前被授权管理 _owner 的所有 Token,则返回 true
balanceOf(address _owner, uint256 _id)返回 _owner 持有的 _id Token 余额。
balanceOfBatch(address[] _owners, uint256[] _ids)在一次调用中返回多个 (owner, id) 对的余额。两个数组长度必须相同,否则调用 revert。
safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes _data)_value_id Token 从 _from 转账到 _to。若 _to 是合约,则调用 onERC1155Received 并检查返回值。
safeBatchTransferFrom(address _from, address _to, uint256[] _ids, uint256[] _values, bytes _data)safeTransferFrom 的批量版本。若 _to 是合约则调用 onERC1155BatchReceived

事件参考

事件触发时机
TransferSingle(_operator, _from, _to, _id, _value)单 Token 转账成功时触发。铸造(_from = 0x0)和燃烧(_to = 0x0)时也会触发。
TransferBatch(_operator, _from, _to, _ids, _values)批量转账成功时触发。
ApprovalForAll(_owner, _operator, _approved)setApprovalForAll 切换操作员授权状态时触发。
URI(_value, _id)_id Token 的元数据 URI 更新为 _value 时触发。

接收钩子

通过 safeTransferFromsafeBatchTransferFrom 接收 TRC-1155 Token 的合约必须实现 TRC1155TokenReceiver

interface TRC1155TokenReceiver {
    function onERC1155Received(
        address _operator,
        address _from,
        uint256 _id,
        uint256 _value,
        bytes calldata _data
    ) external returns (bytes4);

    function onERC1155BatchReceived(
        address _operator,
        address _from,
        uint256[] calldata _ids,
        uint256[] calldata _values,
        bytes calldata _data
    ) external returns (bytes4);
}

onERC1155Received(...)

safeTransferFrom 向本合约发送 Token 时触发。合约须返回:

0xf23a6e61  // = bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))

返回任何其他值都会导致转账 revert。

onERC1155BatchReceived(...)

safeBatchTransferFrom 向合约发送 Token 时调用。合约必须返回:

0xbc197c81  // = bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))

TRC-165 supportsInterface

实现 TRC1155TokenReceiver 的合约还应实现 TRC-165 内省:

function supportsInterface(bytes4 interfaceID) external view returns (bool) {
    return interfaceID == 0x01ffc9a7   // TRC-165 本身
        || interfaceID == 0x4e2312e0;  // TRC1155TokenReceiver
}
interfaceID标识的接口
0x01ffc9a7TRC-165——bytes4(keccak256("supportsInterface(bytes4)"))
0x4e2312e0TRC1155TokenReceiver——两个钩子选择器的异或值

元数据扩展(可选)

TRC-1155 合约可通过 URI 事件和可选的 uri(uint256) 函数暴露每个 Token 的元数据 URI。URI 字符串可包含 Token ID 替换模式 {id},客户端将其替换为 Token ID 的小写十六进制表示(零填充至 64 个字符)。

interface ITRC1155Metadata_URI {
    function uri(uint256 _id) external view returns (string memory);
}

URI 指向的元数据 JSON 遵循与 TRC-721 元数据类似的结构——namedescriptionimage 以及任何自定义 attributes


参考实现片段(非标准,源自 OpenZeppelin)

以上标准仅规定了协议接口。支持这些接口的合约逻辑——包括铸造辅助函数、内部余额记账以及安全转账接受检查——由实现方定义。以下代码片段是从 OpenZeppelin 的 TRC-1155 参考实现中提取的常见模式。

内部存储结构

contract TRC1155 is ITRC1155, ITRC165 {
    // id => (owner => balance)
    mapping (uint256 => mapping(address => uint256)) internal balances;

    // owner => (operator => approved)
    mapping (address => mapping(address => bool)) internal operatorApproval;
}

setApprovalForAll 实现

function setApprovalForAll(address _operator, bool _approved) external {
    operatorApproval[msg.sender][_operator] = _approved;
    emit ApprovalForAll(msg.sender, _operator, _approved);
}

带接收者钩子检查的 safeTransferFrom

bytes4 constant public TRC1155_ACCEPTED = 0xf23a6e61;
bytes4 constant internal TRC1155_BATCH_ACCEPTED = 0xbc197c81;

function safeTransferFrom(
    address _from,
    address _to,
    uint256 _id,
    uint256 _value,
    bytes calldata _data
) external {
    require(_to != address(0x0), "_to cannot be the zero address");
    require(_from == msg.sender || operatorApproval[_from][msg.sender] == true, "Need operator approval");

    balances[_id][_from] = balances[_id][_from] - _value;
    balances[_id][_to]   = balances[_id][_to]   + _value;

    emit TransferSingle(msg.sender, _from, _to, _id, _value);

    // 若目标地址是合约,则调用 onERC1155Received。
    if (isContract(_to)) {
        require(
            TRC1155TokenReceiver(_to).onERC1155Received(msg.sender, _from, _id, _value, _data) == TRC1155_ACCEPTED,
            "contract returned an unknown value from onERC1155Received"
        );
    }
}

mintFungible 辅助函数(非标准扩展)

mintFungible 不是 TRC-1155 标准的一部分——铸造由实现方决定。以下模式是 TRC-1155 下同质化 Token 铸造的常见做法:

function mintFungible(uint256 _id, address[] calldata _to, uint256[] calldata _quantities) external creatorOnly(_id) {
    require(isFungible(_id));

    for (uint256 i = 0; i < _to.length; ++i) {
        address to = _to[i];
        uint256 quantity = _quantities[i];

        balances[_id][to] = quantity + balances[_id][to];

        // 0x0 源地址表示铸造。
        emit TransferSingle(msg.sender, address(0x0), to, _id, quantity);

        if (isContract(to)) {
            _doSafeTransferAcceptanceCheck(msg.sender, msg.sender, to, _id, quantity, '');
        }
    }
}

mintNonFungible 辅助函数(非标准扩展)

mintNonFungible 同样是实现方的模式——它为列表中每个地址铸造一个 NFT:

function mintNonFungible(uint256 _type, address[] calldata _to) external creatorOnly(_type) {
    require(isNonFungible(_type));

    uint256 index = maxIndex[_type] + 1;
    maxIndex[_type] = _to.length + maxIndex[_type];

    for (uint256 i = 0; i < _to.length; ++i) {
        address dst = _to[i];
        uint256 id  = _type | index + i;

        nfOwners[id] = dst;

        emit TransferSingle(msg.sender, address(0x0), dst, id, 1);

        if (isContract(dst)) {
            _doSafeTransferAcceptanceCheck(msg.sender, msg.sender, dst, id, 1, '');
        }
    }
}

相关资源