Skip to content
On this page

L1-14: ERC-1155 Game Items(マルチトークン標準)

1. 課題

ERC-1155 マルチトークン標準コントラクトを実装します。単一のコントラクトで複数のトークンタイプ(代替可能トークン FT + 非代替可能トークン NFT)を同時に管理し、バッチ転送、バッチ残高照会、および Owner による新規トークンタイプの作成とミントをサポートします。

典型的なゲームシナリオ:1つのコントラクトに金貨(FT、代替可能)、レアスキン(NFT、1点限定)、ポーション(セミ FT、100本限定)が同時に存在します。

2. なぜ重要か

ERC-721 ではトークンの種類ごとに個別のコントラクトが必要です。数百種類のアイテムが存在するゲームでは、ガスコストが許容できません。ERC-1155 はこの問題を解決します。

  • 単一コントラクトで複数トークン:1つのコントラクトで理論上無制限のトークンタイプを管理
  • バッチ操作safeBatchTransferFrom で1回のトランザクションで複数トークンを転送 -- 約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)オペレーターに全トークンの管理を承認

トークン作成

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(個別承認)はありません。つまり、あるオペレーターを一度承認すると、そのオペレーターはあなたのすべてのトークンタイプを操作できるようになります。

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 し(ガスの無駄)、さらに悪い結果を招く可能性があります。

6. 落とし穴の解決方法

  • 複数配列を受け取るすべての関数の冒頭で検証する:require(ids.length == amounts.length, "length mismatch")
  • 完全な _doSafeTransferAcceptanceCheck_doSafeBatchTransferAcceptanceCheck を実装する
  • supportsInterface が正しいインターフェース ID を返す:type(IERC1155).interfaceId
  • transferFrom ではなく safeTransferFrom を使用する(ERC-1155 標準には safe バージョンのみ存在)
  • createTokenTransferSingle イベントを発行する(from = address(0) はミントを表す)

7. 技術的ポイント

ポイント説明
単一コントラクト複数トークンtokenId でタイプを区別し、複数コントラクトのデプロイを代替
バッチ転送safeBatchTransferFrom で複数トークンを一度に転送
バッチ照会balanceOfBatch で複数の残高を一度に照会
FT + NFT 混在tokenId の総供給量が FT (>1) か NFT (=1) かを決定
IERC1155Receiverコントラクト受信側が実装すべき安全コールバックインターフェース
URI システムトークンタイプごとに独立したメタデータ URI
Approval モデルsetApprovalForAll オールオアナッシング承認

Built with AiAda