# [ERC1155] ERC1155を1からわかりやすく解説

> ERC1155は、ERC20が持つ特徴とERC721が持つ「非代替性」の2つを兼ね備えたトークン規格になります。

- URL: https://cardene.dev/articles/c3ef92722b057161bebd
- 公開日: 2025-11-19
- 更新日: 2025-11-19
- トピック: Blockchain
- タグ: ethereum, solidity, smartcontract, ブロックチェーン

## はじめに

初めまして。
『DApps開発入門』という本や色々記事を書いているかるでねです。

https://amzn.asia/d/gxvJ0Pw

以下でも情報発信しているので、興味ある記事があればぜひ読んでみてください！

https://cardene.notion.site/ERC-EIP-2a03fa3ea33d43baa9ed82288f98d4a9?pvs=4

https://twitter.com/cardene777

https://zenn.dev/heku

https://cardene.substack.com/

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58

今回は『ERC1155』についてまとめていきます。

:::message
「**ERC1155**」を理解する前に「**ERC20**」と「**ERC721**」を理解しておくことをおすすめします。
以下の記事で「**ERC20**」と「**ERC721**」についてまとめているので、是非読んでみてください！
:::

[\[ERC20\] ERC20を1からわかりやすく解説](/articles/aeefe2d89dd5332c9f43)

[\[ERC721\] ERC721を1からわかりやすく解説](/articles/0570a8d05bc7c9b9593f)

## ERC1155とは

**ERC1155**は、**ERC20**が持つ特徴と**ERC721**が持つ「**非代替性**」の2つを兼ね備えたトークン規格になります。

#### ERC1155の特徴

**ERC1155**は「**セミファンジブルトークン**」と言われていて、簡単にいうと「**特定の `tokenId` のトークンを複数管理することができるトークン**」です。
では特徴を1つずつ確認していきましょう。

**ERC721**では、1つの `tokenId` に1つのトークンが紐づきます。
しかし、**ERC1155**は 1つの `tokenId` に**ERC20**のように複数のトークンを紐づけることができます。
これにより、1つの `tokenId` に紐づいたトークンをまとめて送付することができるようになります。

**例**

例えばゲーム内の剣や盾、鎧などの複数アイテムをマーケットプレイスで販売するとします。
**ERC721**規格であればアイテムを1つずつ管理する必要があります。
例えば、`tokenId` 1~100が剣、`tokenId` 101~200が盾などのように。
ここで**ERC1155**を使用することで、`tokenId=1` は剣、`tokenId=2` は盾のようにまとめて管理することができます。

## EIP1155

#### EIPとは

前章まで**ERC1155**といってきましたが、**EIP1155**とは何でしょうか？

**EIP**とは「**Ethereum Improvement Proposals**」の略で、「**Ethereumの新しい機能やプロセスに関する提案を規定する標準規格**」のことです。
EIPの細かい説明は以下に書かれています。

https://eips.ethereum.org/

簡単に言うと以下になります。

**EIP**は、Ethereum Improvement Proposalsの略でイーサリアムをより良いものにするために議論される改善提案のこと。
イーサリアムは、特定の誰かによって管理されるものではないため、世界中の誰もが**EIP**を提出することで、イーサリアムの発展に貢献することができる。

1155という数字は1155番目の提案ということです。

以下が**EIP1155**についてまとめられているページですが、これをすべて説明するとだいぶ長くなってしまうので、必須機能部分のみこの章でまとめていきます。

https://eips.ethereum.org/EIPS/eip-1155

#### EIP1155の必須関数

では次に**EIP1155**の必須関数について確認していきます。

##### `safeTransferFrom()`

```js
function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data) external;
```

`_id` で指定したトークンを `_value` で指定した量だけ、`_from` で指定したアドレスから `_to` で指定したアドレスに送付する関数。

**条件**

- `_to` には0アドレスを指定できない
- `_from` で指定したアドレスは `_id` で指定したトークンの所有者か、`_id` で指定したトークンの操作権限が与えられたアドレスでなければいけない
- `_from` で指定したアドレスは、`_id` で指定したトークンを `_value` 以上所有していないければいけない
- `_to` にコントラクトのアドレスを渡す時は、送り先のコントラクトで `IERC1155Receiver` コントラクトの `onERC1155Received()` 関数を実装してある必要がある

##### `safeBatchTransferFrom()`

```js
function safeBatchTransferFrom(address _from, address _to, uint256[] calldata _ids, uint256[] calldata _values, bytes calldata _data) external;
```

複数のトークンを効率的に送ることができる関数。

`_ids` で指定した各トークンを `_values` で指定した配列内の同じインデックス番号の値だけ、`_from` で指定したアドレスから `_to` で指定したアドレスに送ることができます。

**例**

```js
_ids = [5, 8, 13]
_values = [100, 20, 50]
```

上記の場合以下のようになります。

- IDが5のトークンを100個 `_from` で指定したアドレスから `_to` に指定したアドレスへ送る
- IDが8のトークンを20個 `_from` で指定したアドレスから `_to` に指定したアドレスへ送る
- IDが13のトークンを50個 `_from` で指定したアドレスから `_to` に指定したアドレスへ送る

**条件**

- `_ids` と `_values` の配列の長さは同じでなければいけない
- `_to` にコントラクトのアドレスを渡す時は、送り先のコントラクトで `IERC1155Receiver` コントラクトの `onERC1155Received()` 関数を実装してある必要がある

##### `balanceOf()`

```js
function balanceOf(address _owner, uint256 _id) external view returns (uint256);
```

`_owner` に指定したアドレスが、`_id` で指定したIDを持つトークンをどれくらい所有しているか返す関数。

**条件**

- `_owner` に0アドレスを指定できない

##### `balanceOfBatch()`

```js
function balanceOfBatch(address[] calldata _owners, uint256[] calldata _ids) external view returns (uint256[] memory);
```

`_owners` で指定した各アドレスが所有している `_ids` で指定した配列内の同じインデックス番号のIDを持つトークンの量を返す関数。

**例**

```js
_owners = [0x123..., 0xabc..., 0xXYZ...]
_ids = [5, 8, 13]
```

上記の場合以下を配列にして返します。

- `0x123...` が所有しているIDが5のトークンの量
- `0xabc...` が所有しているIDが8のトークンの量
- `0xXYZ...` が所有しているIDが13のトークンの量

**条件**

- `_owners` 配列と `_ids` 配列の長さは同じでなければならない

##### `setApprovalForAll()`

```js
function setApprovalForAll(address _operator, bool _approved) external;
```

`setApprovalForAll()` 関数を実行したアドレスが所有する**ERC1155**トークンを、`_operator` で指定したアドレスに送付する権限を与える、もしくは取り除く関数。

`_approved` の値が `true` ならば送付する権限を与え、`_approved` の値が `false` ならば送付する権限を取り除く。

**条件**

- `_operator` のアドレスが `setApprovalForAll()` 関数を実行したアドレスと異なる必要がある

##### `isApprovedForAll()`

```js
function isApprovedForAll(address _owner, address _operator) external view returns (bool);
```

`_operator` で指定したアドレスが、`_owner` で指定したアドレスが所有する**ERC1155**トークンの送付権限があるか確認する関数。

送付権限があれば `true` が返され、なければ `false` が返される。

#### EIP1155のイベント

次に**EIP1155**のイベントについて確認していきます。

##### `TransferSingle`

```js
event TransferSingle(address indexed _operator, address indexed _from, address indexed _to, uint256 _id, uint256 _value);
```

**ERC1155**トークンが送付された時に発行される。

##### `TransferBatch`

```js
event TransferBatch(address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _values);
```

複数の**ERC1155**トークンが送付された時に発行される。

##### `ApprovalForAll`

```js
event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);
```

`_operator` で指定したアドレスに、`_owner` アドレスが所有する**ERC1155**トークンの送付する許可を与えた時、もしくは取り除いた時に発行される。

##### `URI`

```js
event URI(string _value, uint256 indexed _id);
```

`_id` で指定されたIDの**ERC1155**トークンのURIが `_value` で指定した値に変更された時に発行される。

このイベントで発行される値は、`IERC1155MetadataURI` コントラクトの `uri()` 関数が返す値と等しくなる。

#### ERC1155TokenReceiver

次に**ERC1155TokenReceiver**について確認していきます。

##### `onERC1155Received()`

```js
function onERC1155Received(address _operator, address _from, uint256 _id, uint256 _value, bytes calldata _data) external returns(bytes4);
```

`safeTransferFrom()` 関数が安全に実行されるか確認する関数。

送り先がコントラクトの場合、**ERC1155**をサポートしていないと永久に送った**ERC1155**トークンにアクセスできなくなる。
これを防ぐために、送り先のコントラクトが**ERC1155**をサポートしているかを確認しています。

**条件**

- `_operator` に渡されたアドレスは、`safeTransferFrom()` 関数実行アドレスから送付を許可されたEOAアドレス、もしくはコントラクトアドレスでなければいけない
- `_from` に渡されたアドレスは、送る**ERC1155**トークンの所有者でなければならない
- **ERC1155**トークンを発行する場合は `_from` には0アドレスが渡されなければならない
- `_id` に渡されたIDを持つ**ERC1155**トークンは、送られる**ERC1155**トークンでなければならない
- `_value` に渡された値は、`_from` で指定したアドレスが所有する**ERC1155**のトークン残高が減少し、受信者の**ERC1155**が増加しなければならない
- `_data` に渡された値は、`_from` に渡されたアドレスから送付によって渡されたデータの内容が変更されていない状態でないといけない
- 受信側のコントラクトでは、`bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)")` を返した場合、送付は完了していなければならない
- 受信側のコントラクトで `revert` を呼び出して、**ERC1155**の送付を拒否した場合、トランザクションは元に戻されなければならない
- `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)")` 以外が返された時はトランザクションを元に戻さなければならない
- `onERC1155Received()` 関数は1回のトランザクションで複数回呼び出して良いが以下の条件を満たす必要がある
  - 各呼び出しは相互に排他的である必要がある
  - トランザクション中に発生した**ERC1155**トークンの残高変更は、送付順に記述する
- コントラクト自身に**ERC1155**トークンを送る場合は `onERC1155Received()` 関数の呼び出しを省略しても良い

##### `onERC1155BatchReceived()`

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

`safeBatchTransferFrom()` 関数が安全に実行されるか確認する関数。

**条件**

- `_operator` に渡されたアドレスは、`safeTransferFrom()` 関数実行アドレスから送付を許可されたEOAアドレス、もしくはコントラクトアドレスでなければいけない
- `_from` に渡されたアドレスは、送る**ERC1155**トークンの所有者でなければならない
- **ERC1155**トークンを発行する場合は `_from` には0アドレスが渡されなければならない
- `_ids` に渡された各IDを持つ**ERC1155**トークンは、送られる**ERC1155**トークンでなければならない
- `_values` に渡された各値は、`_from` で指定したアドレスが所有する**ERC1155**のトークン残高が減少し、受信者の**ERC1155**が増加しなければならない
- `_data` に渡された値は、`_from` に渡されたアドレスから送付によって渡されたデータの内容が変更されていない状態でないといけない
- 受信側のコントラクトでは、`bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)")` を返した場合、送付は完了していなければならない
- 受信側のコントラクトで `revert` を呼び出して、**ERC1155**の送付を拒否した場合、トランザクションは元に戻されなければならない
- `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)")` 以外が返された時はトランザクションを元に戻さなければならない
- `onERC1155Received()` 関数は1回のトランザクションで複数回呼び出して良いが以下の条件を満たす必要がある
  - 各呼び出しは相互に排他的である必要がある
  - トランザクション中に発生した**ERC1155**トークンの残高変更は、送付順に記述する
- コントラクト自身に**ERC1155**トークンを送る場合は `onERC1155Received()` 関数の呼び出しを省略しても良い

#### ERC1155Metadata_URI

最後に必須の機能ではないですが、**ERC1155Metadata_URI**について説明していきます。

##### `uri()`

```js
function uri(uint256 _id) external view returns (string memory);
```

`_id` で指定されたIDの**ERC1155**トークンのメタデータを返す関数。

メタデータとは、トークンの名前や詳細、画像などの情報がまとめられたものです。

**条件**

- イベントが発行されていない場合、メタデータを取得するために実行されるべきである
- `_id` のイベントが発行された場合、そのイベントと同じ値を返さなければならない
- `_id` で指定したトークンが存在するかどうかのために使用してはいけない
  - 理由としては、トークンが存在しない場合でも何らかの値を返すことができてしまうから

### メタデータについて

**ERC1155**のコードを確認する前に一度メタデータについて確認していきます。
前章でも説明したようにメタデータとは、トークンの名前や詳細、画像などの情報がまとめられたものです。

#### IDについて

メタデータのURI（トークン識別子）に `{id}` という文字列が存在する場合、トークンのIDを16進数に置き換える必要があります。

この時以下のことに気をつける必要があります。

**条件**

- 16進数のIDは小文字の英数字で、先頭に `0x` をつけてはいけない
- 16進数のIDの先頭部分を0でパディングする

ちなみに置き換えた値は256ビットになります。

実際の値は以下のようになります。

**ID例**

```
0000000000000000000000000000000100000000000000000000000000000000
1000000000000000000000000000000100000000000000000000000000000003
1000000000000000000000000000000100000000000000000000000000000007
1000000000000000000000000000000200000000000000000000000000000001
```

読みにくいですね…。
1つずつ確認していきましょう。

まずは32文字（128ビット）ずつで、2つに分割します。
そうすると、1つ目は `00000000000000000000000000000001` と `00000000000000000000000000000000` の2つに分けることができます。

前半の `00000000000000000000000000000001` はトークンのIDを表しています。
トークンIDは1で、先ほどの条件から先頭部分を0でパディングしています。
また、FTの場合は先頭が0になり、NFTに場合は先頭に1がつきます。

後半の `00000000000000000000000000000000` はトークンのindexを表しています。
0番目の**ERC1155**トークンであることがわかります。

では他の3つの値についても確認してみましょう。

- `1000000000000000000000000000000100000000000000000000000000000003` なので、トークンIDが1のNFTで、indexは3です
- `1000000000000000000000000000000100000000000000000000000000000007` なので、トークンIDが1のNFTで、indexは7です
- `1000000000000000000000000000000200000000000000000000000000000001` なので、トークンIDが2のNFTで、indexは1です

以下のURIがあるとします。

```
https://token-domain/{id}.json
```

この時トークンIDを置き換えると以下のようになります。

```
https://token-domain/1000000000000000000000000000000200000000000000000000000000000001.json
```

ここまでURIについて説明してきましたが、上記の実装は必須ではありません。
そのため1つの例だと認識してもらえると良いです。

#### メタデータ

メタデータはJSON形式で定義されます。

**ERC1155**でのJSONスキーマは、以下の「**ERC721 Metadata JSON Schema**」に基づいています。

```json
{
    "title": "Token Metadata",
    "type": "object",
    "properties": {
        "name": {
            "type": "string",
            "description": "Identifies the asset to which this token represents"
        },
        "decimals": {
            "type": "integer",
            "description": "The number of decimal places that the token amount should display - e.g. 18, means to divide the token amount by 1000000000000000000 to get its user representation."
        },
        "description": {
            "type": "string",
            "description": "Describes the asset to which this token represents"
        },
        "image": {
            "type": "string",
            "description": "A URI pointing to a resource with mime type image/* representing the asset to which this token represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive."
        },
        "properties": {
            "type": "object",
            "description": "Arbitrary properties. Values may be strings, numbers, object or arrays."
        }
    }
}
```

**ERC1155**でのメタデータのJSONファイルは以下のようになります。

```json
{
    "name": "Asset Name",
    "description": "Lorem ipsum...",
    "image": "https://s3.amazonaws.com/your-bucket/images/{id}.png",
    "properties": {
        "simple_property": "example value",
        "rich_property": {
            "name": "Name",
            "value": "123",
            "display_value": "123 Example Value",
            "class": "emphasis",
            "css": {
                "color": "#ffffff",
                "font-weight": "bold",
                "text-decoration": "underline"
            }
        },
        "array_property": {
            "name": "Name",
            "value": [1,2,3,4],
            "class": "emphasis"
        }
    }
}
```

また、全ての言語間で表現の統一性を高めるために標準化されるべきです。
そのため、以下のように `localization` という値を持たせることができます。

```json
{
    "title": "Token Metadata",
    "type": "object",
    "properties": {
        "name": {
            "type": "string",
            "description": "Identifies the asset to which this token represents"
        },
        "decimals": {
            "type": "integer",
            "description": "The number of decimal places that the token amount should display - e.g. 18, means to divide the token amount by 1000000000000000000 to get its user representation."
        },
        "description": {
            "type": "string",
            "description": "Describes the asset to which this token represents"
        },
        "image": {
            "type": "string",
            "description": "A URI pointing to a resource with mime type image/* representing the asset to which this token represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive."
        },
        "properties": {
            "type": "object",
            "description": "Arbitrary properties. Values may be strings, numbers, object or arrays."
        },
        "localization": {
            "type": "object",
            "required": ["uri", "default", "locales"],
            "properties": {
                "uri": {
                    "type": "string",
                    "description": "The URI pattern to fetch localized data from. This URI should contain the substring `{locale}` which will be replaced with the appropriate locale value before sending the request."
                },
                "default": {
                    "type": "string",
                    "description": "The locale of the default data within the base JSON"
                },
                "locales": {
                    "type": "array",
                    "description": "The list of locales for which data is available. These locales should conform to those defined in the Unicode Common Locale Data Repository (http://cldr.unicode.org/)."
                }
            }
        }
    }
}
```

上記を実際に使用すると以下のようになります。

```json
{
  "name": "Advertising Space",
  "description": "Each token represents a unique Ad space in the city.",
  "localization": {
    "uri": "ipfs://QmWS1VAdMD353A6SDk9wNyvkT14kyCiZrNDYAad4w1tKqT/{locale}.json",
    "default": "en",
    "locales": ["en", "es", "fr", "ja"]
  }
}
```

**es.json**

```json
{
  "name": "Espacio Publicitario",
  "description": "Cada token representa un espacio publicitario único en la ciudad."
}
```

**fr.json**

```json
{
  "name": "Espace Publicitaire",
  "description": "Chaque jeton représente un espace publicitaire unique dans la ville."
}
```

**ja.json**

```json
{
  "name": "Espace Publicitaire",
  "description": "それぞれのトークンは、街で唯一の広告スペースを表しています。"
}
```

## ERC1155のコード確認

前章ではメタデータを確認してきました。
ここまで来れば**ERC1155**の理解がだいぶ深まってきたと思います。
この章では実際に**ERC1155**の実装コードを1つずつ確認していきたいと思います。

### IERC1155.sol

**EIP1155**で確認した実装する上で必須の関数をここで定義しています。
コードは以下になります。

```js
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC1155/IERC1155.sol)

pragma solidity ^0.8.0;

import "../../utils/introspection/IERC165.sol";

/**
 * @dev Required interface of an ERC1155 compliant contract, as defined in the
 * https://eips.ethereum.org/EIPS/eip-1155[EIP].
 *
 * _Available since v3.1._
 */
interface IERC1155 is IERC165 {
    /**
     * @dev Emitted when `value` tokens of token type `id` are transferred from `from` to `to` by `operator`.
     */
    event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);

    /**
     * @dev Equivalent to multiple {TransferSingle} events, where `operator`, `from` and `to` are the same for all
     * transfers.
     */
    event TransferBatch(
        address indexed operator,
        address indexed from,
        address indexed to,
        uint256[] ids,
        uint256[] values
    );

    /**
     * @dev Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to
     * `approved`.
     */
    event ApprovalForAll(address indexed account, address indexed operator, bool approved);

    /**
     * @dev Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI.
     *
     * If an {URI} event was emitted for `id`, the standard
     * https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees] that `value` will equal the value
     * returned by {IERC1155MetadataURI-uri}.
     */
    event URI(string value, uint256 indexed id);

    /**
     * @dev Returns the amount of tokens of token type `id` owned by `account`.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     */
    function balanceOf(address account, uint256 id) external view returns (uint256);

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {balanceOf}.
     *
     * Requirements:
     *
     * - `accounts` and `ids` must have the same length.
     */
    function balanceOfBatch(
        address[] calldata accounts,
        uint256[] calldata ids
    ) external view returns (uint256[] memory);

    /**
     * @dev Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`,
     *
     * Emits an {ApprovalForAll} event.
     *
     * Requirements:
     *
     * - `operator` cannot be the caller.
     */
    function setApprovalForAll(address operator, bool approved) external;

    /**
     * @dev Returns true if `operator` is approved to transfer ``account``'s tokens.
     *
     * See {setApprovalForAll}.
     */
    function isApprovedForAll(address account, address operator) external view returns (bool);

    /**
     * @dev Transfers `amount` tokens of token type `id` from `from` to `to`.
     *
     * Emits a {TransferSingle} event.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - If the caller is not `from`, it must have been approved to spend ``from``'s tokens via {setApprovalForAll}.
     * - `from` must have a balance of tokens of type `id` of at least `amount`.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
     * acceptance magic value.
     */
    function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes calldata data) external;

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {safeTransferFrom}.
     *
     * Emits a {TransferBatch} event.
     *
     * Requirements:
     *
     * - `ids` and `amounts` must have the same length.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
     * acceptance magic value.
     */
    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] calldata ids,
        uint256[] calldata amounts,
        bytes calldata data
    ) external;
}
```

`IERC1155.sol` で定義されている必須関数は以下になります。

**関数**

- `balanceOf()`
- `balanceOfBatch()`
- `setApprovalForAll()`
- `isApprovedForAll()`
- `safeTransferFrom()`
- `safeBatchTransferFrom()`

これらの関数は**EIP1155**の章で確認したので、ここでの説明は省きます。

#### Interface

コードを確認すると、`interface` となっていますがこれは何でしょうか？

`interface` とは、コントラクトに似ていますが実行することはできません。
`interface` には関数名や引数などのみ定義されていて中身は一切定義されていません。
そのため、`interface` を継承したコントラクト内で関数を再度定義して中身を記述する必要があります。

「**interfaceいらなくね？**」

こう思う方もいると思います。

`interface` を使用するメリットは、実装で必要な関数を確認できることにあります。
最初で述べたように、`interface` には実装する上で必要な関数が定義されているので、開発者やコントラクトを実行するユーザーからどんな機能があるのかを簡単に確認できます。

有名なプロジェクトでも `interface` はよく使用されているので、この機会に理解しておくと後々役に立ちます。

公式ドキュメントは以下になります。

https://solidity-jp.readthedocs.io/ja/latest/contracts.html#interfaces

### IERC1155Receiver.sol

`IERC1155Receiver` で確認した実装する上で必須の関数をここで定義しています。
コードは以下になります。

```js
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC1155/IERC1155Receiver.sol)

pragma solidity ^0.8.0;

import "../../utils/introspection/IERC165.sol";

/**
 * @dev _Available since v3.1._
 */
interface IERC1155Receiver is IERC165 {
    /**
     * @dev Handles the receipt of a single ERC1155 token type. This function is
     * called at the end of a `safeTransferFrom` after the balance has been updated.
     *
     * NOTE: To accept the transfer, this must return
     * `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`
     * (i.e. 0xf23a6e61, or its own function selector).
     *
     * @param operator The address which initiated the transfer (i.e. msg.sender)
     * @param from The address which previously owned the token
     * @param id The ID of the token being transferred
     * @param value The amount of tokens being transferred
     * @param data Additional data with no specified format
     * @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed
     */
    function onERC1155Received(
        address operator,
        address from,
        uint256 id,
        uint256 value,
        bytes calldata data
    ) external returns (bytes4);

    /**
     * @dev Handles the receipt of a multiple ERC1155 token types. This function
     * is called at the end of a `safeBatchTransferFrom` after the balances have
     * been updated.
     *
     * NOTE: To accept the transfer(s), this must return
     * `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`
     * (i.e. 0xbc197c81, or its own function selector).
     *
     * @param operator The address which initiated the batch transfer (i.e. msg.sender)
     * @param from The address which previously owned the token
     * @param ids An array containing ids of each token being transferred (order and length must match values array)
     * @param values An array containing amounts of each token being transferred (order and length must match ids array)
     * @param data Additional data with no specified format
     * @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed
     */
    function onERC1155BatchReceived(
        address operator,
        address from,
        uint256[] calldata ids,
        uint256[] calldata values,
        bytes calldata data
    ) external returns (bytes4);
}
```

`IERC1155Receiver.sol` に定義されている関数は以下になります。

**関数**

- `onERC1155Received()`
- `onERC1155BatchReceived()`

この部分も**EIP1155**の章で説明したので、この章での説明は省きます。

### ERC1155.sol

では次に大本命の `ERC1155.sol` のコードについてみていきましょう。

コードが長すぎるので記事内には全てのコードを載せません。
全体のコードは以下で確認できます。

https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC1155/ERC1155.sol

定義されている関数は以下になります。

**関数一覧**

- `supportsInterface(bytes4 interfaceId)`
- `uri(uint256)`
- `balanceOf(address account, uint256 id)`
- `balanceOfBatch(address[] memory accounts, uint256[] memory ids)`
- `setApprovalForAll(address operator, bool approved)`
- `isApprovedForAll(address account, address operator)`
- `safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes memory data)`
- `safeBatchTransferFrom(address from, address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data)`
- `_safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes memory data)`
- `_safeBatchTransferFrom(address from, address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data)`
- `_setURI(string memory newuri)`
- `_mint(address to, uint256 id, uint256 amount, bytes memory data)`
- `_mintBatch(address to, uint256[] memory ids, uint256[] amounts, bytes memory data)`
- `_burn(address from, uint256 id, uint256 amount)`
- `_burnBatch(address from, uint256[] memory ids, uint256[] memory amounts)`
- `_setApprovalForAll(address owner, address operator, bool approved)`
- `_beforeTokenTransfer(address operator, address from, address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data)`
- `_afterTokenTransfer(address operator, address from, address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data)`
- `_doSafeTransferAcceptanceCheck(address operator, address from, address to, uint256 id, uint256 amount, bytes memory data)`
- `_doSafeBatchTransferAcceptanceCheck(address operator, address from, address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data)`
- `_asSingletonArray(uint256 element)`

長いですね…。
それでは1つずつ確認していきましょう。

**※IERC1155で定義されている関数の説明は、EIP1155の章で解説しているため省略しています。**

#### 変数

```js
// Mapping from token ID to account balances
mapping(uint256 => mapping(address => uint256)) private _balances;

// Mapping from account to operator approvals
mapping(address => mapping(address => bool)) private _operatorApprovals;

// Used as the URI for all token types by relying on ID substitution, e.g. https://token-cdn-domain/{id}.json
string private _uri;
```

まずは変数から確認していきましょう。

##### `_balances`

特定のアドレスがトークンIDをどれだけ所有するか記録する配列。

##### `_operatorApprovals`

指定したアドレスに、特定のアドレスが所有する**ERC1155**トークン全てを送付する権限があるかないかを記録する配列。

##### `_uri`

URIを格納するプライベートな変数。

##### `Constructor`

```js
constructor(string memory uri_) {
    _setURI(uri_);
}
```

`uri_` で指定した文字列をURIにセットする関数。

`constructor` は、コントラクトがデプロイされた時に一度だけ実行されます。

##### `_setURI()`

```js
function _setURI(string memory newuri) internal virtual {
    _uri = newuri;
}
```

`newuri` に指定された文字列を `_uri` 変数に格納する関数。

##### `supportsInterface()`

```js
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
    return
        interfaceId == type(IERC1155).interfaceId ||
        interfaceId == type(IERC1155MetadataURI).interfaceId ||
        super.supportsInterface(interfaceId);
}
```

コントラクトが `interfaceId` に渡された `interface` を実装しているか確認する関数。

`IERC1155` か、`IERC1155MetadataURI`、もしくは継承元が `interfaceId` に渡された `interface` を実装しているか確認しています。

##### `uri()`

```js
function uri(uint256) public view virtual override returns (string memory) {
    return _uri;
}
```

`_uri` 変数を返す関数。

##### `balanceOf()`

```js
function balanceOf(address account, uint256 id) public view virtual override returns (uint256) {
    require(account != address(0), "ERC1155: address zero is not a valid owner");
    return _balances[id][account];
}
```

`id` で指定したIDの**ERC1155**トークンを `account` で指定したアドレスがどれくらい所有しているか返す関数。

##### `balanceOfBatch()`

```js
function balanceOfBatch(
    address[] memory accounts,
    uint256[] memory ids
) public view virtual override returns (uint256[] memory) {
    require(accounts.length == ids.length, "ERC1155: accounts and ids length mismatch");

    uint256[] memory batchBalances = new uint256[](accounts.length);

    for (uint256 i = 0; i < accounts.length; ++i) {
        batchBalances[i] = balanceOf(accounts[i], ids[i]);
    }

    return batchBalances;
}
```

`ids` 配列で指定した各IDの**ERC1155**トークンを `accounts` で指定したアドレスの配列内の同じindexのアドレスがどれくらい所有しているか返す関数。

##### `_setApprovalForAll()`

```js
function _setApprovalForAll(address owner, address operator, bool approved) internal virtual {
    require(owner != operator, "ERC1155: setting approval status for self");
    _operatorApprovals[owner][operator] = approved;
    emit ApprovalForAll(owner, operator, approved);
}
```

`operator` に指定したアドレスに、`owner` で指定したアドレスが所有する**ERC1155**トークンの送付権限を与える、もしくは取り除く関数。

`ApprovalForAll` イベントを発行しています。

##### `setApprovalForAll()`

```js
function setApprovalForAll(address operator, bool approved) public virtual override {
    _setApprovalForAll(_msgSender(), operator, approved);
}
```

`setApprovalForAll()` を実行したユーザーのアドレスを渡して、`_setApprovalForAll()` 関数を実行する関数。

##### `_msgSender()`

```js
function _msgSender() internal view virtual returns (address) {
    return msg.sender;
}
```

`msg.sender` を返す関数。

##### `isApprovedForAll()`

```js
function isApprovedForAll(address account, address operator) public view virtual override returns (bool) {
    return _operatorApprovals[account][operator];
}
```

`operator` に指定したアドレスが、`account` で指定したアドレスが所有する**ERC1155**トークンの送付権限があるか確認する関数。

##### `_beforeTokenTransfer()`

```js
function _beforeTokenTransfer(
    address operator,
    address from,
    address to,
    uint256[] memory ids,
    uint256[] memory amounts,
    bytes memory data
) internal virtual {}
```

送付を実行する前に呼び出される関数。

何かオプションで処理を追加したい場合は、この関数に処理を追記してください。

##### `_afterTokenTransfer()`

```js
function _afterTokenTransfer(
    address operator,
    address from,
    address to,
    uint256[] memory ids,
    uint256[] memory amounts,
    bytes memory data
) internal virtual {}
```

送付を実行した後に呼び出される関数。

何かオプションで処理を追加したい場合は、この関数に処理を追記してください。

##### `_asSingletonArray()`

```js
function _asSingletonArray(uint256 element) private pure returns (uint256[] memory) {
    uint256[] memory array = new uint256[](1);
    array[0] = element;

    return array;
}
```

要素が1つの配列を作成し、`element` に渡された値を格納する関数。

1種類の**ERC1155**トークンを送る場合に、`safeBatchTransferFrom()` 関数と同じ引数で `_beforeTokenTransfer()` 関数や `_afterTokenTransfer()` 関数を実行できるようにするために使用されます。

##### `_doSafeTransferAcceptanceCheck()`

```js
function _doSafeTransferAcceptanceCheck(
    address operator,
    address from,
    address to,
    uint256 id,
    uint256 amount,
    bytes memory data
) private {
    if (to.isContract()) {
        try IERC1155Receiver(to).onERC1155Received(operator, from, id, amount, data) returns (bytes4 response) {
            if (response != IERC1155Receiver.onERC1155Received.selector) {
                revert("ERC1155: ERC1155Receiver rejected tokens");
            }
        } catch Error(string memory reason) {
            revert(reason);
        } catch {
            revert("ERC1155: transfer to non-ERC1155Receiver implementer");
        }
    }
}
```

受け取り手がコントラクトの時、**ERC1155**を実装しているコントラクトであるか確認する関数。

`ERC1155Receiver` コントラクトの `onERC1155Received()` 関数を実行して確認しています。

この記事の初めの方でも説明しましたが、**ERC1155**をサポートしていないコントラクトに**ERC1155**トークンを送ってしまうと2度と誰も触れない状態になってしまいます。

##### `_safeTransferFrom()`

```js
function _safeTransferFrom(
    address from,
    address to,
    uint256 id,
    uint256 amount,
    bytes memory data
) internal virtual {
    require(to != address(0), "ERC1155: transfer to the zero address");

    address operator = _msgSender();
    uint256[] memory ids = _asSingletonArray(id);
    uint256[] memory amounts = _asSingletonArray(amount);

    _beforeTokenTransfer(operator, from, to, ids, amounts, data);

    uint256 fromBalance = _balances[id][from];
    require(fromBalance >= amount, "ERC1155: insufficient balance for transfer");
    unchecked {
        _balances[id][from] = fromBalance - amount;
    }
    _balances[id][to] += amount;

    emit TransferSingle(operator, from, to, id, amount);

    _afterTokenTransfer(operator, from, to, ids, amounts, data);

    _doSafeTransferAcceptanceCheck(operator, from, to, id, amount, data);
}
```

`_balances` 配列の値を更新し、`_doSafeTransferAcceptanceCheck()` 関数を実行して、`from` で指定したアドレスから `to` に指定したアドレスへ、`id` で指定したIDの**ERC1155**トークンを `amount` 分送る関数。

##### `safeTransferFrom()`

```js
function safeTransferFrom(
    address from,
    address to,
    uint256 id,
    uint256 amount,
    bytes memory data
) public virtual override {
    require(
        from == _msgSender() || isApprovedForAll(from, _msgSender()),
        "ERC1155: caller is not token owner or approved"
    );
    _safeTransferFrom(from, to, id, amount, data);
}
```

`from` で指定したアドレスが `id` で指定したIDの**ERC1155**トークンの所有者、もしくは送付権限があるか確認し、`_safeTransferFrom()` 関数を実行する関数。

##### `_doSafeBatchTransferAcceptanceCheck()`

```js
function _doSafeBatchTransferAcceptanceCheck(
    address operator,
    address from,
    address to,
    uint256[] memory ids,
    uint256[] memory amounts,
    bytes memory data
) private {
    if (to.isContract()) {
        try IERC1155Receiver(to).onERC1155BatchReceived(operator, from, ids, amounts, data) returns (
            bytes4 response
        ) {
            if (response != IERC1155Receiver.onERC1155BatchReceived.selector) {
                revert("ERC1155: ERC1155Receiver rejected tokens");
            }
        } catch Error(string memory reason) {
            revert(reason);
        } catch {
            revert("ERC1155: transfer to non-ERC1155Receiver implementer");
        }
    }
}
```

受け取り手がコントラクトの時、**ERC1155**を実装しているコントラクトであるか確認する関数。

`ERC1155Receiver` コントラクトの `onERC1155BatchReceived()` 関数を実行して確認しています。

この記事の初めの方でも説明しましたが、**ERC1155**をサポートしていないコントラクトに**ERC1155**トークンを送ってしまうと2度と誰も触れない状態になってしまいます。

##### `_safeBatchTransferFrom()`

```js
function _safeBatchTransferFrom(
    address from,
    address to,
    uint256[] memory ids,
    uint256[] memory amounts,
    bytes memory data
) internal virtual {
    require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");
    require(to != address(0), "ERC1155: transfer to the zero address");

    address operator = _msgSender();

    _beforeTokenTransfer(operator, from, to, ids, amounts, data);

    for (uint256 i = 0; i < ids.length; ++i) {
        uint256 id = ids[i];
        uint256 amount = amounts[i];

        uint256 fromBalance = _balances[id][from];
        require(fromBalance >= amount, "ERC1155: insufficient balance for transfer");
        unchecked {
            _balances[id][from] = fromBalance - amount;
        }
        _balances[id][to] += amount;
    }

    emit TransferBatch(operator, from, to, ids, amounts);

    _afterTokenTransfer(operator, from, to, ids, amounts, data);

    _doSafeBatchTransferAcceptanceCheck(operator, from, to, ids, amounts, data);
}
```

`ids` 配列と `amounts` 配列の要素分、`_balances` 配列の値を更新し、`_doSafeTransferAcceptanceCheck()` 関数を実行して、`from` で指定したアドレスから `to` に指定したアドレスへ、`id` に渡されたIDの**ERC1155**トークンを `amount` に渡された分送る関数。

##### `safeBatchTransferFrom()`

```js
function safeBatchTransferFrom(
    address from,
    address to,
    uint256[] memory ids,
    uint256[] memory amounts,
    bytes memory data
) public virtual override {
    require(
        from == _msgSender() || isApprovedForAll(from, _msgSender()),
        "ERC1155: caller is not token owner or approved"
    );
    _safeBatchTransferFrom(from, to, ids, amounts, data);
}
```

`from` で指定したアドレスが `id` で指定したIDの**ERC1155**トークンの所有者、もしくは送付権限があるか確認し、`_safeBatchTransferFrom()` 関数を実行する関数。

##### `_mint()`

```js
function _mint(address to, uint256 id, uint256 amount, bytes memory data) internal virtual {
    require(to != address(0), "ERC1155: mint to the zero address");

    address operator = _msgSender();
    uint256[] memory ids = _asSingletonArray(id);
    uint256[] memory amounts = _asSingletonArray(amount);

    _beforeTokenTransfer(operator, address(0), to, ids, amounts, data);

    _balances[id][to] += amount;
    emit TransferSingle(operator, address(0), to, id, amount);

    _afterTokenTransfer(operator, address(0), to, ids, amounts, data);

    _doSafeTransferAcceptanceCheck(operator, address(0), to, id, amount, data);
}
```

新たに**ERC1155**トークンを発行する関数。

**条件**

- `to` に0アドレスは指定できない
- 送り先のアドレスがコントラクトの場合、送り先のコントラクトでは `IERC1155Receiver` コントラクトの `onERC1155Received()` 関数を実装する必要がある

```js
require(to != address(0), "ERC1155: mint to the zero address");
```

送り先のアドレスが0アドレスではないか確認しています。

```js
address operator = _msgSender();
uint256[] memory ids = _asSingletonArray(id);
uint256[] memory amounts = _asSingletonArray(amount);
```

`_mint()` 関数の実行ユーザーアドレスを取得し、`id` と `amount` を要素が1つの配列に変換して、それぞれ `ids` と `amounts` に格納しています。

```js
_beforeTokenTransfer(operator, address(0), to, ids, amounts, data);

_balances[id][to] += amount;
emit TransferSingle(operator, address(0), to, id, amount);

_afterTokenTransfer(operator, address(0), to, ids, amounts, data);
```

`_balances` 配列の値の更新と、`_beforeTokenTransfer()` 関数と `_afterTokenTransfer()` 関数の実行をしています。

```js
_doSafeTransferAcceptanceCheck(operator, address(0), to, id, amount, data);
```

`_doSafeTransferAcceptanceCheck()` 関数を実行して、`to` で指定したアドレスに `amount` 分の `id` で指定したIDの**ERC1155**トークンを送っています。

##### `_mintBatch()`

```js
function _mintBatch(
    address to,
    uint256[] memory ids,
    uint256[] memory amounts,
    bytes memory data
) internal virtual {
    require(to != address(0), "ERC1155: mint to the zero address");
    require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");

    address operator = _msgSender();

    _beforeTokenTransfer(operator, address(0), to, ids, amounts, data);

    for (uint256 i = 0; i < ids.length; i++) {
        _balances[ids[i]][to] += amounts[i];
    }

    emit TransferBatch(operator, address(0), to, ids, amounts);

    _afterTokenTransfer(operator, address(0), to, ids, amounts, data);

    _doSafeBatchTransferAcceptanceCheck(operator, address(0), to, ids, amounts, data);
}
```

新たに複数の**ERC1155**トークンを発行する関数。

処理は `_mint()` 関数と同じで、`ids` 配列と `amounts` 配列分 `_mint()` 関数と同じ処理を繰り返しています。

**条件**

- `ids` 配列と `amounts` 配列の長さは同じでなければならない
- 送り先のアドレスがコントラクトの場合、送り先のコントラクトでは `IERC1155Receiver` コントラクトの `onERC1155Received()` 関数を実装する必要がある

##### `_burn()`

```js
function _burn(address from, uint256 id, uint256 amount) internal virtual {
    require(from != address(0), "ERC1155: burn from the zero address");

    address operator = _msgSender();
    uint256[] memory ids = _asSingletonArray(id);
    uint256[] memory amounts = _asSingletonArray(amount);

    _beforeTokenTransfer(operator, from, address(0), ids, amounts, "");

    uint256 fromBalance = _balances[id][from];
    require(fromBalance >= amount, "ERC1155: burn amount exceeds balance");
    unchecked {
        _balances[id][from] = fromBalance - amount;
    }

    emit TransferSingle(operator, from, address(0), id, amount);

    _afterTokenTransfer(operator, from, address(0), ids, amounts, "");
}
```

`from` で指定したアドレスが所有する**ERC1155**トークンを燃やす関数。

`_balances` 配列の値は更新しているが、実際に**ERC1155**トークンを燃やしていないです。
そのため、`_afterTokenTransfer()` 関数に具体的な処理を書くことで**ERC1155**トークンを燃やすことができます。

**条件**

- `from` に0アドレスを指定できない
- `from` で指定したアドレスは `id` で指定したIDの**ERC1155**トークンを `amount` で指定した量以上所有していないといけない

##### `_burnBatch()`

```js
function _burnBatch(address from, uint256[] memory ids, uint256[] memory amounts) internal virtual {
    require(from != address(0), "ERC1155: burn from the zero address");
    require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");

    address operator = _msgSender();

    _beforeTokenTransfer(operator, from, address(0), ids, amounts, "");

    for (uint256 i = 0; i < ids.length; i++) {
        uint256 id = ids[i];
        uint256 amount = amounts[i];

        uint256 fromBalance = _balances[id][from];
        require(fromBalance >= amount, "ERC1155: burn amount exceeds balance");
        unchecked {
            _balances[id][from] = fromBalance - amount;
        }
    }

    emit TransferBatch(operator, from, address(0), ids, amounts);

    _afterTokenTransfer(operator, from, address(0), ids, amounts, "");
}
```

`ids` 配列と `amounts` 配列の要素分、`from` で指定したアドレスが所有する**ERC1155**トークンを燃やす関数。

`_balances` 配列の値は更新しているが、実際に**ERC1155**トークンを燃やしていないです。
そのため、`_afterTokenTransfer()` 関数に具体的な処理を書くことで**ERC1155**トークンを燃やすことができます。

**条件**

- `ids` 配列と `amounts` 配列の長さは同じでなければならない

### ERC1155の実装

では**ERC1155**の実装をしていきましょう。
以下が**ERC1155**の実装です。

```js
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

import "@openzeppelin/contracts@4.8.0/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts@4.8.0/access/Ownable.sol";

contract CardeneToken is ERC1155, Ownable {

    constructor() ERC1155("https://cardene.net/erc1115/{id}.json") {
        _mint(msg.sender, 0, 10000, "");
        _mint(msg.sender, 1, 1, "");
        _mint(msg.sender, 2, 10**9, "");
    }
}
```

「**え、すくな…**」

と思った方も少なくないはずです。

ここまで説明してきた関数の数々がパッとみないように見えます。
しかし、7行目のコントラクト定義部分で、`ERC1155.sol` をしっかり継承しています。
そのためここまで説明してきた関数を使用することができます。

最低限の実装はこれでできるので、ぜひ自分でカスタマイズなどしてみてください。

### ERC1155の実行

**ERC1155**の実装を確認したところで、最後に実行をしていきましょう！
今回はRemix.ideというブラウザで使用できるエディタを使用していきます。

https://remix.ethereum.org/

以下のOpenzeppelinのサイトから簡単に**ERC1155**を実装できるので、時間ある方はぜひ触ってみてください。

https://wizard.openzeppelin.com/#erc1155

では早速コードを書いていきましょう！

#### ファイルの作成

Remixエディタでファイルを作成してください。
ファイルが作成できたら以下を作成したファイルに貼り付けてください。

```js
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

import "@openzeppelin/contracts@4.8.0/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts@4.8.0/access/Ownable.sol";

contract CardeneToken is ERC1155, Ownable {

    constructor() ERC1155("https://cardene.net/erc1115/{id}.json") {
        _mint(msg.sender, 0, 10000, "");
        _mint(msg.sender, 1, 1, "");
        _mint(msg.sender, 2, 10**9, "");
    }
}
```

#### 実行

では実行していきます。

いかがでしょうか？
しっかりデプロイできましたでしょうか？
`ERC1155.sol` 内の関数がちゃんと実行できることも確認できたはずです。

#### Goerliテストネットにデプロイ

:::message alert
2025年11月現在Goerli Testnetは停止しているので、Sepolia Testnetなどを使用してください。
:::

最後にGoerliテストネットにデプロイして、**ERC1155**トークンを作成してみましょう！

![名称未設定のデザイン.gif](/images/qiita-96eea089-6ba4-4b84-808c-69f7b40214cf.webp)

いかがだったでしょうか？
しっかりテストネットにデプロイできているのが確認できましたね。

## 最後に

今回は『**ERC1155**』についてまとめてきました。
他でも色々記事を書いているのでぜひよろしければ読んでいってください！

https://amzn.asia/d/gxvJ0Pw

https://cardene.notion.site/EIP-2a03fa3ea33d43baa9ed82288f98d4a9?source=copy_link

https://zenn.dev/heku

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58

https://twitter.com/cardene777

https://cardene.substack.com/

### 参考

- https://nft-media.net/beginner/erc1155/20623/
- https://www.metaverse-style.com/trend/7853
- https://gaiax-blockchain.com/erc1155
- https://medium.com/axell-corporation/erc1155-multi-tokenの標準規格-661da7e0cfa1
- https://ethereum.org/ja/developers/docs/standards/tokens/erc-1155/
- https://docs.openzeppelin.com/contracts/3.x/erc1155
- https://mirror.xyz/blueplanet42.eth/VLiQfXCPGNRnYCsJc5yzR_qIcV4kYPy705wHH88o5HY
- https://lab.stir.network/article/erc1155
- https://eips.ethereum.org/EIPS/eip-1155
- https://docs.openzeppelin.com/contracts/3.x/api/token/erc1155
