[ERC5700] NFTにNFTを紐付けたり解除する仕組みを理解しよう！

# はじめに

初めまして。
**CryptoGames**というブロックチェーンゲーム企業でエンジニアをしている **cardene（かるでね）** です！
スマートコントラクトを書いたり、フロントエンド・バックエンド・インフラと幅広く触れています。

https://cryptogames.co.jp/

代表的なゲームは**クリプトスペルズ**というブロックチェーンゲームです。

https://cryptospells.jp/

今回は、NFTにNFTを紐付けたり（バインド）、紐付けを解除する（アンバインド）する仕組みを提案する木あっ苦である**ERC5700**についてまとめていきます！

:::note info
以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。
:::

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

:::note warn
**5700**は現在（2023年9月27日）では「**Draft**」段階です。
:::

# 概要

この規格は、仮想世界やデジタルアセットの新しい仕組みを提供するものです。
**ERC721**や**ERC1155**という種類のトークンを「**バインダブル**」と呼び、これらのトークンを**ERC721** NFT（非代替性トークン）に関連付ける方法を提供します。

:::note info
**ERC721**については以下を参考にしてください。
:::

https://chaldene.net/erc721

:::note info
**ERC1155**については以下を参考にしてください。
:::

https://chaldene.net/erc1155

バインダブルトークン**ERC721**NFTに「**バインド**」すると、そのバインダブルトークンの所有権がNFTに移ります。
しかし、NFTの所有者はいつでもバインダブルトークンを「**アンバインド**」でき、それらのトークンの所有権を取り戻すことができます。
この仕組みにより、バインダブルトークンはバインドされたNFTと一緒に移動でき、追加の費用なしでトークンとNFTをまとめて取引できます。
これにより、1つのNFTに対して複数のトークンを束ねたり、簡単に転送したりできるようになります。
バインドされたトークンは、アンバインドされるまでロック状態にあり、アンバインド後には通常のトークンの機能を再び使用できます。

この規格は、さまざまな用途に適しています。

### NFTに結びつけられた物理的なアセット
例えば、マイクロチップが埋め込まれたストリートウェア、デジタル化された車のコレクション、デジタルツイン技術を使用した不動産など。

### NFTに結びつけられたデジタルアセット
例えば、仮想的なウォードローブにアクセサリーを追加できる、組み合わせ可能な音楽トラック、カスタマイズ可能なメタバースの土地など。

この規格により、異なるトークンとNFTを組み合わせて新しい創造的なアプリケーションを開発するのが容易になり、デジタルエンターテイメントやアセットの分野で革新的な機会が広がります。

# 動機

NFT（非代替性トークン）と通常のトークンを組み合わせて使う時、スムーズで効率的な方法を提供するための標準的なインターフェースがあります。
このインターフェースにより、NFTとトークンを一緒に束ねて転送するプロセスが簡単に行え、ウォレット、マーケットプレイス、および他のNFTアプリケーションとの互換性が確保されます。
また、このインターフェースを使うことで、トークンの所有権に関する具体的で厳格な戦略を実装する必要がありません。

この標準規格は、通常のトークンの所有権を特定のアカウントに紐づけるのではなく、NFTのレベルでトークンの所有権を扱うことを目指しています。
具体的には、NFTとトークンを組み合わせて、一緒に取引できるようにするための汎用的な方法を提供します。
この規格は、既存の**ERC721**や**ERC1155**規格と連携し、これらの規格との互換性を確保します。

この規格を使用することで、NFTとトークンを組み合わせて取引するプロセスがスムーズになり、ウォレット、マーケットプレイス、および他のNFT関連アプリケーションとの間で一貫性のある方法で操作できます。
この規格の利用により、トークンの所有権に関する複雑な実装を避けつつ、NFTとトークンを簡単に連携させることができます。

# 仕様

## ERC721 Bindable

**ERC721**バインダブル標準を実装するスマートコントラクトは、**IERC721Bindable**を実装する必要があります。

また、**IER721Bindable**インターフェースを実装する開発者には、`supportsInterface`と呼ばれる特定の関数に対するルールがあります。
この関数は、特定の識別子（`0x82a34a7d`）を渡された場合`true`を返します。

```javascript
/// @title ERC-721 Bindable Token Standard
/// @dev See https://eips.ethereum.org/ERCS/eip-5700
///  Note: the ERC-165 identifier for this interface is 0x82a34a7d.
interface IERC721Bindable /* is IERC721 */ {

    /// @notice This event emits when an unbound token is bound to an NFT.
    /// @param operator The address approved to perform the binding.
    /// @param from The address of the unbound token owner.
    /// @param bindAddress The contract address of the NFT being bound to.
    /// @param bindId The identifier of the NFT being bound to.
    /// @param tokenId The identifier of binding token.
    event Bind(
        address indexed operator,
        address indexed from,
        address indexed bindAddress,
        uint256 bindId,
        uint256 tokenId
    );

    /// @notice This event emits when an NFT-bound token is unbound.
    /// @param operator The address approved to perform the unbinding.
    /// @param from The owner of the NFT the token is bound to.
    /// @param to The address of the new unbound token owner.
    /// @param bindAddress The contract address of the NFT being unbound from.
    /// @param bindId The identifier of the NFT being unbound from.
    /// @param tokenId The identifier of the unbinding token.
    event Unbind(
        address indexed operator,
        address indexed from,
        address to,
        address indexed bindAddress,
        uint256 bindId,
        uint256 tokenId
    );

    /// @notice Binds token `tokenId` to NFT `bindId` at address `bindAddress`.
    /// @dev The function MUST throw unless `msg.sender` is the current owner, 
    ///  an authorized operator, or the approved address for the token. It also
    ///  MUST throw if the token is already bound or if `from` is not the token
    ///  owner. Finally, it MUST throw if the NFT contract does not support the
    ///  ERC-721 interface or if the NFT being bound to does not exist. Before 
    ///  binding, token ownership MUST be transferred to the contract address of
    ///  the NFT. On bind completion, the function MUST emit `Transfer` & `Bind` 
    ///  events to reflect the implicit token transfer and subsequent bind.
    /// @param from The address of the unbound token owner.
    /// @param bindAddress The contract address of the NFT being bound to.
    /// @param bindId The identifier of the NFT being bound to.
    /// @param tokenId The identifier of the binding token.
    function bind(
        address from,
        address bindAddress,
        uint256 bindId,
        uint256 tokenId
    ) external;

    /// @notice Unbinds token `tokenId` from NFT `bindId` at address `bindAddress`.
    /// @dev The function MUST throw unless `msg.sender` is the current owner, 
    ///  an authorized operator, or the approved address for the NFT the token
    ///  is bound to. It also MUST throw if the token is unbound, if `from` is
    ///  not the owner of the bound NFT, or if `to` is the zero address. After
    ///  unbinding, token ownership MUST be transferred to `to`, during which
    ///  the function MUST check if `to` is a valid contract (code size > 0),
    ///  and if so, call `onERC721Received`, throwing if the wrong identifier is
    ///  returned. On unbind completion, the function MUST emit `Unbind` &
    ///  `Transfer` events to reflect the unbind and subsequent transfer.
    /// @param from The address of the owner of the NFT the token is bound to.
    /// @param to The address of the unbound token new owner.
    /// @param bindAddress The contract address of the NFT being unbound from.
    /// @param bindId The identifier of the NFT being unbound from.
    /// @param tokenId The identifier of the unbinding token.
    function unbind(
        address from,
        address to,
        address bindAddress,
        uint256 bindId,
        uint256 tokenId
    ) external;

    /// @notice Gets the NFT address and identifier token `tokenId` is bound to.
    /// @dev When the token is unbound, this function MUST return the zero
    ///  address for the address portion to indicate no binding exists.
    /// @param tokenId The identifier of the token being queried.
    /// @return The token-bound NFT contract address and numerical identifier.
    function binderOf(uint256 tokenId) external view returns (address, uint256);

    /// @notice Gets total tokens bound to NFT `bindId` at address `bindAddress`.
    /// @param bindAddress The contract address of the NFT being queried.
    /// @param bindId The identifier of the NFT being queried.
    /// @return The total number of tokens bound to the queried NFT.
    function boundBalanceOf(address bindAddress, uint256 bindId) external view returns (uint256);
```
### `Binder`

```javascript
struct Binder {
    address bindAddress;
    uint256 bindId;
}
```

**概要**
バインダブルトークンがバインドされたNFTコントラクトのアドレスと識別子の構造体。

**パラメータ**
- `bindAddress`
    - バインダブルトークンがバインドされたNFTコントラクトのアドレス。
- `bindId`
    - バインダブルトークンがバインドされたNFTの識別子。

---

### `boundBalanceOf`

```javascript
mapping(address => mapping(uint256 => uint256)) public boundBalanceOf; 
```

**概要**
トークンバインディングによってバインドされたNFTごとのトークンのバランスを追跡する配列。

**パラメータ**
- `address`
    - ユーザーのアドレス。
- `uint256`
    - NFTの識別子。

---

### `_bound`

```javascript
mapping(uint256 => Binder) internal _bound;
```

**概要**
NFTとそれにバインドされたバインダブルトークンの情報を追跡する配列。

**パラメータ**
- `uint256`
    - NFTの識別子。
- `Binder`
    - バインドされたNFTコントラクトのアドレスと識別子の構造体。

---

### `_ERC165_INTERFACE_ID`

```javascript
bytes4 private constant _ERC165_INTERFACE_ID = 0x01ffc9a7;
```

**概要**
**EIP165**インターフェースの識別子定数。

---

### `_ERC721_BINDER_INTERFACE_ID`

```javascript
bytes4 private constant _ERC721_BINDER_INTERFACE_ID = 0x2ac2d2bc;
```

**概要**
**ERC721**バインダーインターフェースの識別子定数。

---

### `_ERC721_BINDABLE_INTERFACE_ID`

```javascript
bytes4 private constant _ERC721_BINDABLE_INTERFACE_ID = 0xd92c3ff0;
```

**概要**
**ERC721**バインダブルインターフェースの識別子定数。

---

### `binderOf`

```javascript
function binderOf(uint256 tokenId) public view returns (address, uint256) {
    Binder memory bound = _bound[tokenId];
    return (bound.bindAddress, bound.bindId);
}
```

**概要**

指定されたトークンIDにバインドされたNFTのアドレスと識別子を取得する関数。

**詳細**

トークンIDを引数として受け取り、内部で`_bound`マップからバインド情報を取得し、その情報を返します。
バインド情報は`Binder`構造体で表され、その構造体からバインドされたNFTのアドレスと識別子を取得して返します。

**引数**

- `tokenId`
  - バインド情報を取得するためのトークンID。

**戻り値**

- `address`
  - トークンがバインドされているNFTのアドレス。
- `uint256`
  - トークンがバインドされているNFTの数値識別子。

### `bind`

```javascript
function bind(
        address from,
        address bindAddress,
        uint256 bindId,
        uint256 tokenId
    ) public {
        if (
            _bound[tokenId].bindAddress != address(0) ||
            IERC721(bindAddress).ownerOf(bindId) == address(0)
        ) {
            revert BindInvalid();
        }

        if (from != _ownerOf[tokenId]) {
            revert OwnerInvalid();
        }

        if (
            msg.sender != from &&
            msg.sender != getApproved[tokenId] &&
            !_operatorApprovals[from][msg.sender]
        ) {
            revert SenderUnauthorized();
        }

        delete getApproved[tokenId];

        unchecked {
            _balanceOf[from]--;
            _balanceOf[bindAddress]++;
            boundBalanceOf[bindAddress][bindId]++;
        }

        _ownerOf[tokenId] = bindAddress;
        _bound[tokenId] = Binder(bindAddress, bindId);

        emit Transfer(from, bindAddress, tokenId);
        emit Bind(msg.sender, from,  bindAddress, bindId, tokenId);

}
```

**概要**

トークンを特定のNFTにバインドする関数。

**詳細**

トークンの所有者（`from`）、バインド先NFTのアドレス（`bindAddress`）、バインド先NFTの数値識別子（`bindId`）、およびバインド対象のトークンID（`tokenId`）を引数として受け取ります。
関数内では、複数の条件チェックを行い、バインド操作の妥当性を確認します。
条件に合致しない場合はリバート（`revert`）されます。

バインド操作が妥当である場合、所有者からトークンが取り除かれ、バインド先NFTのバインドカウントが更新されます。
その後、トークンの所有者がバインド先NFTに変更され、バインド情報が`_bound`マップに格納されます。
最後に、トランザクションイベントが発行されます。

**引数**

- `from`
  - トークンの現在の所有者のアドレス。
- `bindAddress`
  - バインド先NFTのコントラクトアドレス。
- `bindId`
  - バインド先NFTの数値識別子。
- `tokenId`
  - バインド対象のトークンのID。

### `unbind`

```javascript
function unbind(
        address from,
        address to,
        address bindAddress,
        uint256 bindId,
        uint256 tokenId
    ) public {
        Binder memory bound = _bound[tokenId];
        if (
            bound.bindAddress != bindAddress ||
            bound.bindId != bindId ||
            _ownerOf[tokenId] != bindAddress
        ) {
            revert BindInvalid();
        }

        IERC721 binder = IERC721(bindAddress);

        if (from != binder.ownerOf(bindId)) {
            revert OwnerInvalid();
        }

        if (
            msg.sender != from &&
            msg.sender != binder.getApproved(tokenId) &&
            !binder.isApprovedForAll(from, msg.sender)
        ) {
            revert SenderUnauthorized();
        }

        if (to == address(0)) {
            revert ReceiverInvalid();
        }

        delete getApproved[tokenId];

        unchecked {
            _balanceOf[to]++;
            _balanceOf[bindAddress]--;
            boundBalanceOf[bindAddress][bindId]--;
        }

        _ownerOf[tokenId] = to;
        delete _bound[tokenId];

        emit Unbind(msg.sender, from, to, bindAddress, bindId, tokenId);
        emit Transfer(bindAddress, to, tokenId);

		if (
			to.code.length != 0 &&
				IERC721Receiver(to).onERC721Received(msg.sender, bindAddress, tokenId, "")
                !=
                IERC721Receiver.onERC721Received.selector
		) {
			revert SafeTransferUnsupported();
        }

}
```

**概要**

トークンを特定のNFTからアンバインドする関数。

**詳細**

この関数は、トークンの所有者（`from`）、新しい所有者（`to`）、アンバインド元NFTのアドレス（`bindAddress`）、アンバインド元NFTの識別子（`bindId`）、およびアンバインド対象のトークンID（`tokenId`）を引数として受け取ります。
関数内では、複数の条件チェックを行い、アンバインド操作の妥当性を確認します。
条件に合致しない場合はリバート（`revert`）されます。

アンバインド操作が妥当である場合、トークンの所有者が変更され、バインド情報が`_bound`マップから削除されます。
また、トークンの新しい所有者にトークンが転送され、トランザクションイベントが発行されます。
さらに、トークンを受け取る側が**ERC721**レシーバーコントラクトでサポートされている場合、その関数が呼び出されます。

**引数**

- `from`
  - アンバインド元NFTの所有者のアドレス。
- `to`
  - アンバインド後のトークンの新しい所有者のアドレス。
- `bindAddress`
  - アンバインド元NFTのコントラクトアドレス。
- `bindId`
  - アンバインド元NFTの識別子。
- `tokenId`
  - アンバインド対象のトークンのID。

### `supportsInterface`

```javascript
function supportsInterface(bytes4 id) public pure override(ERC721, IERC165) returns (bool) {
    return super.supportsInterface(id) || id == _ERC721_BINDABLE_INTERFACE_ID;
}
```

**概要**

指定されたインターフェースIDがサポートされているか確認する関数。

**詳細**

**ERC721**や**IERC165**のサポートを確認した後、カスタムの`_ERC721_BINDABLE_INTERFACE_ID`がサポートされているかどうかを確認します。
サポートされている場合は`true`を返し、それ以外の場合は`false`を返します。

**引数**

- `id`
  - 確認するインターフェースのID。

**戻り値**

- `bool`
    - 指定されたインターフェースIDがサポートされている場合は`true`。
    - それ以外の場合は`false`。

## ERC1155 Bindable

**ERC1155**バインダブル標準を実装するスマートコントラクトは、**IERC1155Bindable**を実装する必要があります。

また、**IER1155Bindable**インターフェースを実装する開発者には、`supportsInterface`と呼ばれる特定の関数に対するルールがあります。
この関数は、特定の識別子（`0xd0d55c6`）を渡された場合`true`を返します。

```javascript
/// @title ERC-1155 Bindable Token Standard
/// @dev See https://eips.ethereum.org/ERCS/eip-5700
///  Note: the ERC-165 identifier for this interface is 0xd0d555c6.
interface IERC1155Bindable /* is IERC1155 */ {

    /// @notice This event emits when token(s) are bound to an NFT.
    /// @param operator The address approved to perform the binding.
    /// @param from The owner address of the unbound tokens.
    /// @param bindAddress The contract address of the NFT being bound to.
    /// @param bindId The identifier of the NFT being bound to.
    /// @param tokenId The identifier of the binding token type.
    /// @param amount The number of tokens binding to the NFT.
    event Bind(
        address indexed operator,
        address indexed from,
        address indexed bindAddress,
        uint256 bindId,
        uint256 tokenId,
        uint256 amount
    );

    /// @notice This event emits when token(s) of different types are bound to an NFT.
    /// @param operator The address approved to perform the batch binding.
    /// @param from The owner address of the unbound tokens.
    /// @param bindAddress The contract address of the NFTs being bound to.
    /// @param bindId The identifier of the NFT being bound to.
    /// @param tokenIds The identifiers of the binding token types.
    /// @param amounts The number of tokens per type binding to the NFTs.
    event BindBatch(
        address indexed operator,
        address indexed from,
        address indexed bindAddress,
        uint256 bindId,
        uint256[] tokenIds,
        uint256[] amounts
    );

    /// @notice This event emits when token(s) are unbound from an NFT.
    /// @param operator The address approved to perform the unbinding.
    /// @param from The owner address of the NFT the tokens are bound to.
    /// @param to The address of the unbound tokens' new owner.
    /// @param bindAddress The contract address of the NFT being unbound from.
    /// @param bindId The identifier of the NFT being unbound from.
    /// @param tokenId The identifier of the unbinding token type.
    /// @param amount The number of tokens unbinding from the NFT.
    event Unbind(
        address indexed operator,
        address indexed from,
        address to,
        address indexed bindAddress,
        uint256 bindId,
        uint256 tokenId,
        uint256 amount
    );

    /// @notice This event emits when token(s) of different types are unbound from an NFT.
    /// @param operator The address approved to perform the batch binding.
    /// @param from The owner address of the unbound tokens.
    /// @param to The address of the unbound tokens' new owner.
    /// @param bindAddress The contract address of the NFTs being unbound from.
    /// @param bindId The identifier of the NFT being unbound from.
    /// @param tokenIds The identifiers of the unbinding token types.
    /// @param amounts The number of tokens per type unbinding from the NFTs.
    event UnbindBatch(
        address indexed operator,
        address indexed from,
        address to,
        address indexed bindAddress,
        uint256 bindId,
        uint256[] tokenIds,
        uint256[] amounts
    );

    /// @notice Binds `amount` tokens of `tokenId` to NFT `bindId` at address `bindAddress`.
    /// @dev The function MUST throw unless `msg.sender` is an approved operator
    ///  for `from`. It also MUST throw if the `from` owns fewer than `amount`
    ///  tokens. Finally, it MUST throw if the NFT contract does not support the
    ///  ERC-721 interface or if the NFT being bound to does not exist. Before 
    ///  binding, tokens MUST be transferred to the contract address of the NFT. 
    ///  On bind completion, the function MUST emit `Transfer` & `Bind` events 
    ///  to reflect the implicit token transfers and subsequent bind.
    /// @param from The owner address of the unbound tokens.
    /// @param bindAddress The contract address of the NFT being bound to.
    /// @param bindId The identifier of the NFT being bound to.
    /// @param tokenId The identifier of the binding token type.
    /// @param amount The number of tokens binding to the NFT.
    function bind(
        address from,
        address bindAddress,
        uint256 bindId,
        uint256 tokenId,
        uint256 amount
    ) external;

    /// @notice Binds `amounts` tokens of `tokenIds` to NFT `bindId` at address `bindAddress`.
    /// @dev The function MUST throw unless `msg.sender` is an approved operator
    ///  for `from`. It also MUST throw if the length of `amounts` is not the 
    ///  same as `tokenIds`, or if any balances of `tokenIds` for `from` is less
    ///  than that of `amounts`. Finally, it MUST throw if the NFT contract does 
    ///  not support the ERC-721 interface or if the bound NFT does not exist. 
    ///  Before binding, tokens MUST be transferred to the contract address of 
    ///  the NFT. On bind completion, the function MUST emit `TransferBatch` and
    ///  `BindBatch` events to reflect the batch token transfers and bind.
    /// @param from The owner address of the unbound tokens.
    /// @param bindAddress The contract address of the NFTs being bound to.
    /// @param bindId The identifier of the NFT being bound to.
    /// @param tokenIds The identifiers of the binding token types.
    /// @param amounts The number of tokens per type binding to the NFTs.
    function batchBind(
        address from,
        address bindAddress,
        uint256 bindId,
        uint256[] calldata tokenIds,
        uint256[] calldata amounts
    ) external;

    /// @notice Unbinds `amount` tokens of `tokenId` from NFT `bindId` at address `bindAddress`.
    /// @dev The function MUST throw unless `msg.sender` is an approved operator
    ///  for `from`. It also MUST throw if `from` is not the owner of the bound
    ///  NFT, if the NFT's token balance is fewer than `amount`, or if `to` is 
    ///  the zero address. After unbinding, tokens MUST be transferred to `to`,
    ///  during which the function MUST check if `to` is a valid contract (code 
    ///  size > 0), and if so, call `onERC1155Received`, throwing if the wrong \
    ///  identifier is returned. On unbind completion, the function MUST emit 
    ///  `Unbind` & `Transfer` events to reflect the unbind and transfers.
    /// @param from The owner address of the NFT the tokens are bound to.
    /// @param to The address of the unbound tokens' new owner.
    /// @param bindAddress The contract address of the NFT being unbound from.
    /// @param bindId The identifier of the NFT being unbound from.
    /// @param tokenId The identifier of the unbinding token type.
    /// @param amount The number of tokens unbinding from the NFT.
    function unbind(
        address from,
        address to,
        address bindAddress,
        uint256 bindId,
        uint256 tokenId,
        uint256 amount
    ) external;

    /// @notice Unbinds `amount` tokens of `tokenId` from NFT `bindId` at address `bindAddress`.
    /// @dev The function MUST throw unless `msg.sender` is an approved operator
    ///  for `from`. It also MUST throw if the length of `amounts` is not the
    ///  same as `tokenIds`, if any balances of `tokenIds` for the NFT is less 
    ///  than that of `amounts`, or if `to` is the zero addresss. After 
    ///  unbinding, tokens MUST be transferred to `to`, during which the 
    ///  function MUST check if `to` is a valid contract (code size > 0), and if 
    ///  so, call `onERC1155BatchReceived`, throwing if the wrong identifier is 
    ///  returned. On unbind completion, the function MUST emit `UnbindBatch` & 
    ///  `TransferBatch` events to reflect the batch unbind and transfers.
    /// @param from The owner address of the unbound tokens.
    /// @param to The address of the unbound tokens' new owner.
    /// @param bindAddress The contract address of the NFTs being unbound from.
    /// @param bindId The identifier of the NFT being unbound from.
    /// @param tokenIds The identifiers of the unbinding token types.
    /// @param amounts The number of tokens per type unbinding from the NFTs.
    function batchUnbind(
        address from,
        address to,
        address bindAddress,
        uint256 bindId,
        uint256[] calldata tokenIds,
        uint256[] calldata amounts
    ) external;

    /// @notice Gets the number of tokens of type `tokenId` bound to NFT `bindId` at address `bindAddress`.
    /// @param bindAddress The contract address of the bound NFT.
    /// @param bindId The identifier of the bound NFT.
    /// @param tokenId The identifier of the token type bound to the NFT.
    /// @return The number of tokens of type `tokenId` bound to the NFT.
    function boundBalanceOf(
        address bindAddress,
        uint256 bindId,
        uint256 tokenId
    ) external view returns (uint256);

    /// @notice Gets the number of tokens of types `bindIds` bound to NFTs `bindIds` at address `bindAddress`.
    /// @param bindAddress The contract address of the bound NFTs.
    /// @param bindIds The identifiers of the bound NFTs.
    /// @param tokenIds The identifiers of the token types bound to the NFTs.
    /// @return balances The bound balances for each token type / NFT pair.
    function boundBalanceOfBatch(
        address bindAddress,
        uint256[] calldata bindIds,
        uint256[] calldata tokenIds
    ) external view returns (uint256[] memory balances);

}
```

**ERC721Bindable**にない要素のみここでまとめて行きます。 

### `boundBalanceOf`

```javascript
mapping(address => mapping(uint256 => mapping(uint256 => uint256))) public boundBalanceOf;
```

**概要**

アセットの特定のトークンタイプに関連付けられたバウンドバランスを追跡する配列。

**詳細**

このマッピングは3つの階層からなります。
最初の階層はアドレス（`address`）に関連付けられており、特定のアドレスによって所有されたトークンのバインドバランスを追跡します。
2番目の階層は識別子（`uint256`）であり、特定のトークンタイプを表します。
最後の階層はさらに識別子（`uint256`）で、特定のトークンIDを示します。

**パラメータ**

- アドレス
    - トークンの所有者のアドレス。
- トークンタイプ
    - 特定のトークンの種類または識別子。
- トークンID
    - 特定のトークンの一意の識別子。

---

### `boundBalanceOfBatch`

```javascript
function boundBalanceOfBatch(
        address bindAddress,
        uint256[] calldata bindIds,
        uint256[] calldata tokenIds
    ) public view returns (uint256[] memory balances) {
        if (bindIds.length != tokenIds.length) {
            revert ArityMismatch();
        }

        balances = new uint256[](bindIds.length);

        unchecked {
            for (uint256 i = 0; i < bindIds.length; ++i) {
                balances[i] = boundBalanceOf[bindAddress][bindIds[i]][tokenIds[i]];
            }
        }
}
```

**概要**
複数のバインドIDとトークンIDに対するバウンドバランスを一括で取得する関数。

**詳細**

指定された`bindAddress`の下で複数のバインドIDとトークンIDに対するバウンドバランスを取得します。
取得したバランスは配列として返され、各バインドIDとトークンIDに対応するバウンドバランスが含まれています。
もし`bindIds`と`tokenIds`の配列の長さが一致しない場合、エラーとして`revert`されます。

**引数**

- `bindAddress`
  - バインドされているNFTのコントラクトアドレス。
- `bindIds`
  - バインドIDの配列。
- `tokenIds`
  - トークンIDの配列。

**戻り値**

- `balances`
  - バインドIDとトークンIDに対応するバウンドバランスの配列。

---

### `batchBind`

```javascript
function batchBind(
        address from,
        address bindAddress,
        uint256 bindId,
        uint256[] calldata tokenIds,
        uint256[] calldata amounts
    ) public {
        if (msg.sender != from && !isApprovedForAll[from][msg.sender]) {
            revert SenderUnauthorized();
        }

        if (IERC721(bindAddress).ownerOf(bindId) == address(0)) {
            revert BindInvalid();
        }

        if (tokenIds.length != amounts.length) {
            revert ArityMismatch();
        }

        for (uint256 i = 0; i < tokenIds.length; i++) {
            _balanceOf[from][tokenIds[i]] -= amounts[i];
            _balanceOf[bindAddress][tokenIds[i]] += amounts[i];
            boundBalanceOf[bindAddress][bindId][tokenIds[i]] += amounts[i];
        }

		emit TransferBatch(msg.sender, from, bindAddress, tokenIds, amounts);
        emit BindBatch(msg.sender, from, bindAddress, bindId, tokenIds, amounts);

}
```

**概要**

複数のトークンを指定されたNFTにバインドする関数。

**詳細**

指定された所有者（`from`）、バインド先NFTのアドレス（`bindAddress`）、バインド先NFTの数値識別子（`bindId`）、バインドするトークンID（`tokenIds`）、および各トークンIDに対する数量（`amounts`）を引数として受け取ります。
関数内では、所有者の認可を確認し、バインド操作の妥当性を検証します。
また、バインドするトークンIDと数量の配列の長さが一致しない場合、エラーとして`revert`されます。

バインド操作が妥当である場合、所有者からトークンが取り除かれ、バインド先NFTのバインドバランスが更新されます。
最後に、トランザクションイベントが発行されます。

**引数**

- `from`
  - トークンの現在の所有者のアドレス。
- `bindAddress`
  - バインド先NFTのコントラクトアドレス。
- `bindId`
  - バインド先NFTの数値識別子。
- `tokenIds`
  - バインドするトークンのIDの配列。
- `amounts`
  - 各トークンIDに対するバインド数量の配列。

### `batchUnbind`

```javascript
function batchUnbind(
        address from,
        address to,
        address bindAddress,
        uint256 bindId,
        uint256[] calldata tokenIds,
        uint256[] calldata amounts
    ) public {
        IERC721 binder = IERC721(bindAddress);

        if (binder.ownerOf(bindId) != from) {
            revert OwnerInvalid();
        }

        if (
            msg.sender != from &&
            msg.sender != binder.getApproved(bindId) &&
            !binder.isApprovedForAll(from, msg.sender)
        ) {
            revert SenderUnauthorized();
        }

        if (tokenIds.length != amounts.length) {
            revert ArityMismatch();
        }

        if (to == address(0)) {
            revert ReceiverInvalid();
        }

        for (uint256 i = 0; i < tokenIds.length; i++) {

            _balanceOf[to][tokenIds[i]] += amounts[i];
            _balanceOf[bindAddress][tokenIds[i]] -= amounts[i];
            boundBalanceOf[bindAddress][bindId][tokenIds[i]] -= amounts[i];
        }

        emit UnbindBatch(msg.sender, from, to, bindAddress, bindId, tokenIds, amounts);
		emit TransferBatch(msg.sender, from, bindAddress, tokenIds, amounts);

		if (
			to.code.length != 0 &&
				IERC1155Receiver(to).onERC1155BatchReceived(msg.sender, from, tokenIds, amounts, "")
                !=
                IERC1155Receiver.onERC1155BatchReceived.selector
		) {
			revert SafeTransferUnsupported();
        }
}
```

**概要**

複数のトークンを指定されたNFTからアンバインドする関数。

**詳細**

指定された所有者（`from`）、新しい所有者（`to`）、アンバインド元NFTのアドレス（`bindAddress`）、アンバインド元NFTの数値識別子（`bindId`）、アンバインドするトークンID（`tokenIds`）、および各トークンIDに対する数量（`amounts`）を引数として受け取ります。
関数内では、所有者と認可を確認し、アンバインド操作の妥当性を検証します。
また、アンバインドするトークンIDと数量の配列の長さが一致しない場合、エラーとして`revert`されます。

アンバインド操作が妥当である場合、トークンの所有者が変更され、バインドバランスが更新されます。
トランザクションイベントも発行されます。
さらに、トークンを受け取る側がERC-1155バッチレシーバーコントラクトをサポートしている場合、その関数が呼び出されます。

**引数**

- `from`
  - アンバインド元NFTの所有者のアドレス。
- `to`
  - アンバインド後のトークンの新しい所有者のアドレス。
- `bindAddress`
  - アンバインド元NFTのコントラクトアドレス。
- `bindId`
  - アンバインド元NFTの数値識別子。
- `tokenIds`
  - アンバインドするトークンのIDの配列。
- `amounts`
  - 各トークンIDに対するアンバインド数量の配列。

---

# 補足

トークンバインディングの標準は、ウォレット、アプリケーション、プロトコルが異なるデジタルアセットを一緒にまとめ、NFTと組み合わせて操作、取引、表示できる新しい方法を提供します。
これは、**Dopamine**などのシナリオでの実際の応用例があります。
例えば、ストリートウェアの洋服と音楽、アバター、または洋服のデジタルツインなどのデジタルアセットを一緒にパッケージ化することができます。
これらのアセットは、特別なトークンである「**バインダブルトークン**」として表現され、それらをNFTとして表現されるマイクロチップに結びつけることで実現されます。

## バインドメカニズム

バインディングのプロセスでは、バインダブルトークンの所有権がバインドされたNFTに譲渡されます。
同時に、NFTの所有者はいつでもバインディングを解除することができます。
ただし、このデザインの欠点は、まだこの標準を採用していないアプリケーションでは、バンドルされたトークンがNFTの所有者によって所有されているという情報が表示されないことです。

:::note info
バインディングが行われると、バインダブルトークンの所有権は形式的にはNFTに移行します。
しかし、このトークンが実際にNFTの一部であるかどうかを示す方法は、この新しい標準を導入していない古いアプリケーションには存在しない可能性があります。
そのため、上記のようなアプリケーションでは、バンドルされたトークンがNFTの所有者によって所有されていることが正確に表示されません。

この結果、バインディングのメリットを最大限に活用するには、この新しい標準をサポートするアプリケーションが必要です。
それによって、バインダブルトークンとNFTの連携がスムーズに行え、所有権が適切に表示されます。
:::

# 後方互換性

バインド可能なトークン・インターフェースは、既存の**ERC721**や**ERC1155**標準と互換性があります。

# 参考実装

以下に参考実装コードを格納しています。

https://github.com/cardene777/base-code/tree/develop/contract/contracts/ERC5700

引用: https://eips.ethereum.org/EIPS/eip-5700

# セキュリティ考慮事項

バインディングのプロセスにおいて、バインダブルトークンの所有権はバインドされたNFTコントラクトに譲渡されます。
したがって、実装者（開発者）は注意が必要です。
なぜなら、アンバインド（バインド解除）操作は、特定のNFTの所有者だけが実行できるようにする必要があるからです。

:::note info
バインディングが行われると、バインダブルトークンの実際の所有権がバインドされたNFTコントラクトに移ります。
そのため、アンバインド操作は、正確なNFTの所有者によってのみ実行できるなどのセキュリティ対策を行なった上で実行する必要があります。
これにより、他の誤ったユーザーが誤ってアンバインドを実行することを防ぎ、セキュリティを確保できます。
:::

# 引用

Leeren (@leeren), "ERC-5700: Bindable Token Interface [DRAFT]," Ethereum Improvement Proposals, no. 5700, September 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-5700.

# 最後に

今回は「NFTにNFTを紐付けたり（バインド）、紐付けを解除する（アンバインド）する仕組みを提案する木あっ苦である**ERC5700**」についてまとめてきました！
いかがだったでしょうか？

質問などがある方は以下のTwitterのDMなどからお気軽に質問してください！

[Twitter @cardene777](https://twitter.com/cardene777)

他の媒体でも情報発信しているのでぜひ他も見ていってください！

https://chaldene.net/

https://zenn.dev/heku

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58