Skip to content
On this page

L1-14: ERC-1155 Game Items(多代币标准)

1. 问题

实现 ERC-1155 多代币标准合约,在一个合约中同时管理多种代币类型(同质化代币 FT + 非同质化代币 NFT),支持批量转账、批量余额查询,以及 Owner 创建和 mint 新代币类型。

典型的游戏场景:一个合约中同时包含金币(FT,同质化)、稀有皮肤(NFT,限量 1 份)、药水(semi-FT,限量 100 瓶)。

2. 原因

ERC-721 每种代币需要一个独立合约——对于游戏(可能有数百种道具)来说 Gas 成本不可接受。ERC-1155 解决了这个问题:

  • 单合约多代币:一个合约管理理论上无限种代币
  • 批量操作safeBatchTransferFrom 一次交易转移多种代币——Gas 节省约 50%
  • 批量查询balanceOfBatch 一次查询多个地址/代币的余额——前端性能大幅提升
  • FT + NFT 混合:同一个合约可以同时包含同质化和非同质化代币

ERC-1155 是 ERC-721 的互补标准而非替代。它被广泛应用于游戏(Axie Infinity、Gods Unchained)、会员卡、和任何需要多种代币类型的场景。

3. 方案

合约架构

从零实现完整的 IERC-1155 接口(不继承 OpenZeppelin 实现,以加深理解):

solidity
contract GameItems is IERC1155, Ownable {
    // tokenId => owner => balance
    mapping(uint256 => mapping(address => uint256)) private _balances;
    // owner => operator => approved
    mapping(address => mapping(address => bool)) private _operatorApprovals;
    // tokenId => URI
    mapping(uint256 => string) private _tokenURIs;

    uint256 private _tokenIdCounter;
}

核心接口

方法描述
balanceOf(owner, id)查询单个代币余额
balanceOfBatch(owners[], ids[])批量查询
safeTransferFrom(from, to, id, amount, data)单代币转账
safeBatchTransferFrom(from, to, ids[], amounts[], data)批量转账
setApprovalForAll(operator, approved)授权操作者管理所有代币

Token 创建

只有 Owner 可以创建新代币类型:

solidity
function createToken(
    string calldata _uri,
    address[] calldata _recipients,
    uint256[] calldata _amounts
) external onlyOwner returns (uint256) {
    uint256 tokenId = _tokenIdCounter++;
    _tokenURIs[tokenId] = _uri;

    for (uint256 i = 0; i < _recipients.length; i++) {
        _balances[tokenId][_recipients[i]] += _amounts[i];
        emit TransferSingle(msg.sender, address(0), _recipients[i], tokenId, _amounts[i]);
    }
    return tokenId;
}

4. 遭遇的陷阱

4.1 safeTransferFrom 的回调安全检查

ERC-1155 要求转账到合约地址时,接收方必须实现 IERC1155Receiver 接口。如果忘记这个检查,代币会被永久锁在无法处理 ERC-1155 的合约中。

实现必须:

solidity
function _doSafeTransferAcceptanceCheck(...) private {
    if (to.code.length > 0) {
        try IERC1155Receiver(to).onERC1155Received(...) returns (bytes4 response) {
            require(response == IERC1155Receiver.onERC1155Received.selector, "ERC1155 rejected");
        } catch {
            revert("ERC1155 transfer to non-ERC1155Receiver");
        }
    }
}

4.2 批量操作的数组长度验证

safeBatchTransferFrombalanceOfBatch 都接受多个数组参数。必须确保所有数组长度相等,否则会导致数组越界或逻辑错误。

4.3 Approval 模型的差异

ERC-1155 使用 setApprovalForAll(全有或全无),没有 ERC-721 的 approve(单独授权)。这意味着一旦授权某个 operator,它可以操作你的所有代币类型。

4.4 supportsInterface 的实现

ERC-165 接口检测是所有 ERC 标准的要求。如果 supportsInterface 返回值不正确,市场和钱包可能无法识别你的合约是 ERC-1155。

5. 陷阱的原因

5.1

ERC-1155 的安全转账机制是防止代币丢失的关键防线。没有这个检查,用户可能意外将游戏道具转到交易所合约(它不知道如何处理 ERC-1155),导致道具永久丢失。

5.2

Solidity 不会自动检查数组长度。调用者可能传入 ids.length=3amounts.length=2——如果不在函数开头验证,会导致循环访问不存在的索引而 revert(浪费 gas)或更糟。

6. 如何解决陷阱

  • 在所有接受多数组的函数开头验证 require(ids.length == amounts.length, "length mismatch")
  • 实现完整的 _doSafeTransferAcceptanceCheck_doSafeBatchTransferAcceptanceCheck
  • supportsInterface 返回正确的接口 ID:type(IERC1155).interfaceId
  • 使用 safeTransferFrom 而非 transferFrom(ERC-1155 标准中只有 safe 版本)
  • createToken 时发出 TransferSingle 事件(from = address(0) 表示铸造)

7. 技术要点

要点说明
单合约多代币tokenId 区分不同类型,替代多合约部署
批量转账safeBatchTransferFrom 一次转移多种代币
批量查询balanceOfBatch 一次查询多个余额
FT + NFT 混合tokenId 的总供应量决定是 FT(>1) 还是 NFT(=1)
IERC1155Receiver合约接收方必须实现的安全回调接口
URI 系统每种代币独立的 metadata URI
Approval 模型setApprovalForAll 全有或全无授权

Built with AiAda