# [ERC6059] 複数NFTコレクションでネスト構造を作る仕組みを理解しよう！

> 「Parent-Governed Nestable NFT」標準は、ERC721を進化させ、異なるNFT同士の関係や相互作用を可能にする提案です。

- URL: https://cardene.dev/articles/544b01bc14850ecd657c
- 公開日: 2023-08-18
- 更新日: 2023-08-18
- トピック: Blockchain
- タグ: ethereum, solidity, ブロックチェーン, nft

## はじめに

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

https://cryptogames.co.jp/

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

https://cryptospells.jp/

今回は、ネストされたNFTのインターフェースを提供して、NFTに親子関係をもたらす提案である**ERC6059**についてまとめていきます！

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

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

## 概要

「**Parent-Governed Nestable NFT**」標準は、**ERC721**を進化させ、異なるNFT同士の関係や相互作用を可能にする提案です。

この提案の基本アイデアは非常にシンプルです。
通常、NFTの所有者は個人のウォレットやスマートコントラクトですが、この提案によりNFT自体も所有者となることができます。

NFTを別のNFTにネストするプロセスは、別のユーザーにNFTを送るプロセスとほぼ同じです。
親トークンを所有するアカウントがトランザクションを発行することで、あるNFTを別のNFT内にネストできます。

1つのNFTは別の単一のNFTによって所有されることができます。
そして、そのNFT自体も複数の別のNFTを所有することができます。
この提案は、NFT同士の親子関係を構築する枠組みを提供します。
親トークンは他のトークンを所有する側を指し、子トークンは他のトークンに所有される側を指します。
1つのトークンは親であり子でもあり得ます。
特定のトークンの子トークンは親トークンの所有者によって完全に管理されますが、子トークンの提案は誰からでも行える仕組みです。

![eip-6059-nestable-tokens.png](/images/qiita-347c4e54-954a-ab6d-2deb-427b496ca76b.webp)

この図は、子トークンが同時に親トークンであり得る構造を示していますが、
どちらもルート親トークンの所有者によって管理されています。

## 動機
NFT（ノンファンジブルトークン）はEthereumエコシステム内で広く利用され、さまざまな用途に活用されています。
これらのトークンにさらなる機能を標準化する時期が訪れています。
トークンが他のトークンを所有できる能力を持つことで、より大きな有用性、使いやすさ、および将来の互換性が得られます。

**ERC721**が発行されてから4年間、追加の機能が必要とされ、無数の拡張が行われてきました。
このERCは、**ERC721**を以下の点で改善しています。

1. バンドリング
2. コレクション
3. メンバーシップ
4. 委任

### バンドリング
**ERC721**の最も一般的な使用法の一つは、トークンに関連するマルチメディアコンテンツを配布することです。
異なるコレクションからの複数のNFTをバンドルして販売する際、これらを簡単に一緒にまとめて単一のトランザクションで処理する方法は現在存在しません。
この提案は、これを標準化する方法を導入しています。
すべてのトークンを単純なバンドルにまとめ、そのバンドルを販売することで、すべてのトークンのコントロールが一度のトランザクションで買い手に移ることになります。

### コレクション
多くのNFTユーザーは、さまざまな基準に基づいてNFTをコレクションしています。
これは、トークンのユーティリティを求める人、ユニークな特性を重視する人、視覚的な魅力を追求する人など、様々な好みや目的によるものです。
しかし、これまで特定のアカウントに関連するNFTをグループ化するための標準的な方法は存在しませんでした。
この提案は、NFTの所有者の好みに基づいて、それらを効果的にまとめることを可能にします。
親トークンをルートとし、その下に関連するすべての子トークンをネストすることで、特定のグループを表現することができます。

ソウルバウンド（非移転可能）トークンが増えることで、この提案の重要性が増しています。
ソウルバウンドトークンは、特定の特性や属性を持つトークンです。
例えば、供給チェーンの場面では、出荷コンテナを表すNFTがあります。
このNFTは、そのコンテナが旅程の各段階を示す複数の子トークンを持つことができます。
出荷元から出発し、倉庫に到着し、目的地に到着するまでの各ステップを子トークンとしてネストすることで、各段階ごとの情報を効果的に管理できるようになります。
このように、ソウルバウンドトークンを使用することで、特定のプロセスや進行状況をトークン自体が示すことが可能になります。

### メンバーシップ
分散型自治組織（DAO）や閉じられたアクセスグループは、メンバーシップを持つNFTを発行することがあります。
これにより、特定のグループの一員であることを示すNFTを保持することで、その組織やグループの特典や権限を享受できるようになります。

例えば、あるプロジェクトがDAOを運営していて、プロジェクトの意思決定に参加するためにはメンバーシップが必要な場合を考えてみましょう。
通常は、DAOのメンバーシップNFTを保持することで、投票権や報酬の受け取りなどの権限を得ることができます。

提案されている「**Parent-Governed Nestable NFT**」標準によれば、NFTをNFTにネストすることができます。
これを活用することで、メンバーシップNFTと、そのメンバーシップに関連する特典や権限を示す別のNFTを結びつけることができます。
例えば、メンバーシップNFTを親トークンとして設定し、その下に特典や権限を示す子トークンをネストすることで、メンバーシップの権限が視覚的に示されることになります。

組織が新たな特典を追加する際にも、新しい特典を示すNFTを直接メンバーシップNFTにネストして追加することができます。
これにより、新たな特典を獲得するためには単に新しいNFTを追加するだけで済みます。

このように、NFTをトークンにネストする機能を活用することで、メンバーシップや特典の管理が簡素化され、ユーザーが保持するNFTのバリューがより明確になる可能性があります。

### 委任
DAO（分散型自治組織）の中での投票は重要な機能であり、投票の仕組みは様々です。
1つの方法は、メンバーが投票権を持つ代わりに、交換可能な投票トークンを持ち、これを他のメンバーに対して委任することです。
これにより、メンバー同士で投票権を効果的に共有することができます。

例として、あるDAOが新しいプロジェクトに投資するかどうかの意思決定を行う場面を考えてみましょう。
DAOのメンバーは投票トークンを持ち、それぞれが個別に投票を行うことができます。
しかし、一部のメンバーは意志を1つにまとめるために、自分の投票権を信頼できる仲間に委任したいと考えることがあります。

提案されている「**Parent-Governed Nestable NFT**」標準に従えば、この委任投票のプロセスをNFTのネストによってシンプルに表現できます。
各メンバーは自分の投票権を表すNFTを持ち、このNFTを別のNFT（委任先のNFT）にネストすることで、その投票権を他のメンバーに委任することができます。
ネストされたNFTは、委任元のメンバーが意思決定に参加することなく、委任先のメンバーがまとめて投票するための権限を示すものです。

さらに、メンバーが委任を解除する際にも、同じ方法を使用してNFTをトークンから解除し、新たな委任先のNFTにネストすることができます。
これにより、メンバーは簡単に委任の変更を行うことができます。

この仕組みにより、投票権の委任や変更がトークンのネストによってシームレスに行えるため、メンバー同士の意志統一や柔軟な投票プロセスが実現されます。

## 仕様

```javascript
pragma solidity ^0.8.16;

interface IERC6059 /* is ERC165 */ {
    struct DirectOwner {
        uint256 tokenId;
        address ownerAddress;
    }

    event NestTransfer(
        address indexed from,
        address indexed to,
        uint256 fromTokenId,
        uint256 toTokenId,
        uint256 indexed tokenId
    );

    event ChildProposed(
        uint256 indexed tokenId,
        uint256 childIndex,
        address indexed childAddress,
        uint256 indexed childId
    );

    event ChildAccepted(
        uint256 indexed tokenId,
        uint256 childIndex,
        address indexed childAddress,
        uint256 indexed childId
    );

    event AllChildrenRejected(uint256 indexed tokenId);

    event ChildTransferred(
        uint256 indexed tokenId,
        uint256 childIndex,
        address indexed childAddress,
        uint256 indexed childId,
        bool fromPending
    );

    struct Child {
        uint256 tokenId;
        address contractAddress;
    }

    function ownerOf(uint256 tokenId) external view returns (address owner);

    function directOwnerOf(uint256 tokenId)
        external
        view
        returns (
            address,
            uint256,
            bool
        );

    function burn(uint256 tokenId, uint256 maxRecursiveBurns)
        external
        returns (uint256);

    function addChild(uint256 parentId, uint256 childId) external;

    function acceptChild(
        uint256 parentId,
        uint256 childIndex,
        address childAddress,
        uint256 childId
    ) external;

    function rejectAllChildren(uint256 parentId, uint256 maxRejections) external;

    function transferChild(
        uint256 tokenId,
        address to,
        uint256 destinationId,
        uint256 childIndex,
        address childAddress,
        uint256 childId,
        bool isPending,
        bytes data
    ) external;

    function childrenOf(uint256 parentId)
        external
        view
        returns (Child[] memory);

    function pendingChildrenOf(uint256 parentId)
        external
        view
        returns (Child[] memory);

    function childOf(uint256 parentId, uint256 index)
        external
        view
        returns (Child memory);

    function pendingChildOf(uint256 parentId, uint256 index)
        external
        view
        returns (Child memory);

    function nestTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        uint256 destinationId
    ) external;
}
```

:::message alert
ここでの説明に「**ペンディング**」と「**アクティブ**」といいワードが出てきます。
**ペンディング**は、「**保留中**」と訳して処理が途中であると認識してください。
一方、**アクティブ**はそのまま「**アクティブな状態**」で処理が完了して他の処理が実行できる状態であると認識してください。
:::

### 構造体

#### DirectOwner

所有権に関する重要な情報を保持する構造体。
トークンの所有者情報や所有権の階層構造を明確に表現するのに役立ちます。
トークンがNFTによって所有されていない場合、`tokenId`は必ず`0`になります。

**プロパティ**

- `tokenId`
    - 親トークンのID。
- `ownerAddress`
    - 現在のトークンの所有者のアドレス。

#### Child

子トークンに関する情報を保持する構造体。

**プロパティ**

- `tokenId`
    - 子トークンのID。
    - 子トークンのコントラクト内でのトークンID。
- `contractAddress`
    - 子トークンのスマートコントラクトのアドレス。
    - 子トークンが属するコレクションのコントラクトを特定するために使用されます。

### イベント

#### NestTransfer

トークンの所有者が変更された時に発行されるイベント。

- `from`
    - 送付元アドレス。
- `to`
    - 送付先アドレス。
- `fromTokenId`
    - 元の親のトークンID。
    - 親トークンでない場合は`0`となる。
- `toTokenId`
    - 新しい親のトークンID。
    - 親トークンでない場合は`0`となる。
- `tokenId`
    - 送付したNFTのトークンID。

#### ChildProposed

親トークンのペンディング子トークン配列内に、新たに子トークンを追加した時に発行されるイベント。

:::message
ペンディングとは、トークンが特定のアクションや処理を待っている状態のことです。
具体的には以下の状態を指します。

- トークンの所有権が移動するために承認待ちの状態。
- 親トークンが子トークンの提案を待っている状態。

ペンディング状態のトークンは、まだ最終的な確認や処理が行われていないため一時的な状態と言えます。
他のアクションが完了するまで、トークンの状態はペンディングのままです。
:::

- `tokenId`
    - 親トークンのトークンID。
- `childIndex`
    - 親トークンのペンディング子トークンの配列内のインデックス。
- `childAddress`
    - 子トークンのコントラクトのアドレス。
- `childId`
    - 子トークンのコントラクト内でのトークンID。

#### ChildAccepted

親トークンがペンディング子トークンを受け入れ、それをアクティブ子トークン配列に移行した時に発行されるイベント。

:::message
アクティブは、トークンが通常の状態であり、何らかのアクションや処理が完了している状態を指します。
トークンがアクティブ状態の場合、通常の取引や所有権の移転が行えます。

例えば、親トークンが子トークンの提案を受け入れ、アクティブ子トークンとして承認した場合、その子トークンはアクティブ状態となり、所有権の移転などが可能になります。
:::

- `tokenId`
    - 親トークンのトークンID。
- `childIndex`
    - 親トークンのアクティブ子トークンの配列内のインデックス。
- `childAddress`
    - 子トークンのコントラクトのアドレス。
- `childId`
    - 子トークンのコントラクト内でのトークンID。

:::message
**ペンディング**と**アクティブ**の違い
要するに、ペンディングはアクションの完了を待つトークンの状態を表し、アクティブは通常の状態や所有権の移転が可能な状態を指します。
:::

#### AllChildrenRejected

親トークンがすべてのペンディング子トークンを拒否した時に発行されるイベント。

- `tokenId`
    - ペンディング子トークンが拒否された親トークンのID。

#### ChildTransferred

親トークンが子トークンを転送し、所有権が移転された時に発行されるイベント。

- `tokenId`
    - 親トークンのトークンID。
- `childIndex`
    - 転送される子トークンの親トークン配列内のインデックス。
- `childAddress`
    - 転送される子トークンのコントラクトのアドレス。
- `childId`
    - 子トークンのコントラクト内でのトークンID。
- `fromPending`
    - `true`の時、子トークンはペンディング子トークン配列から転送された。
    - `false`の時、子トークンはアクティブ子トークン配列から転送された。

### 関数

#### ownerOf

指定したトークンの「ルート」所有者のアドレスを取得する関数。

**引数**

- `tokenId`
    - ルート所有者を取得するトークンのID。

**戻り値**

トークンのルート所有者のアドレス。

#### directOwnerOf

指定したトークンの直接の所有者のアドレスを取得する関数。

**引数**

- `tokenId`
    - 直接の所有者を取得するトークンのID。

**戻り値**

- トークンの所有者のアドレス。
- 親トークンのID。
    - 所有者がNFTでない場合、`0`になる。
- 所有者がNFTかどうかの`bool`値。

#### burn

指定したトークンを焼却する関数。
紐づいている子トークンも再起的にバーンされます。

**引数**

- `tokenId`
    - 焼却するトークンのID。
- `maxRecursiveBurns`
    - 再帰的な焼却の最大回数。

**戻り値**

再帰的に焼却された子トークンの数。

#### addChild

親トークンに子トークンを追加する関数。
親トークンのペンディング子トークン配列に子トークンが追加されます。

**引数**

- `parentId`
    - 新しい子トークンを追加する親トークンのID。
- `childId`
    - 新しい提案された子トークンの子トークンのコントラクト内でのトークンID。

#### acceptChild

親トークンにペンディングの子トークンを受け入れる関数。
ペンディングの子トークンがアクティブな子トークン配列に移動されます。

**引数**

- `parentId`
    - 子トークンを受け入れる親トークンのID。
- `childIndex`
    - 受け入れる子トークンのペンディング配列のインデックス。
- `childAddress`
    - 子トークンのコレクションのコントラクトアドレス。
- `childId`
    - 子トークンのID。

#### rejectAllChildren

指定した親トークンペンディングの子トークンを全て拒否する関数。

**引数**

- `parentId`
    - 新しい子トークンを追加する親トークンのID。
- `childId`
    - ペンディングの子トークンを全て拒否する親トークンのID。
- `maxRejections`
    - 拒否する予定の子トークンの最大数。

#### transferChild

親トークンから子トークンを転送する関数。
転送される子トークンの所有者は`to`に設定され、`to`が`0x0`アドレスの場合は所有者の更新は行われません。

**引数**

- `tokenId`
    - 子トークンを転送する親トークンのID。
- `to`
    - 子トークンの送り先アドレス。
- `destinationId`
    - 子トークンを受け入れる先のトークンのID。
    - トークンでない場合は`0`となります。
- `childIndex`
    - 転送する子トークンのインデックス。
- `childAddress`
    - 子トークンのコレクションのコントラクトアドレス。
- `childId`
    - 子トークンのID。
- `isPending`
    - 転送される子トークンが親トークンのペンディング配列にあるかどうかの`bool`値。
- data
    - `to`に送信される追加データ。

#### childrenOf

指定した親トークンのアクティブな子トークンを取得する関数。

**引数**

- `parentId`
    - アクティブな子トークンを取得する親トークンのID。

**戻り値**

`Child`構造体の配列。

#### pendingChildrenOf

指定した親トークンのペンディングの子トークンを取得する関数。

**引数**

- `parentId`
    - ペンディングの子トークンを取得する親トークンのID。

**戻り値**

`Child`構造体の配列。

#### childOf

指定した親トークンのアクティブな子トークンのうち、指定したインデックスの子トークンを取得する関数。

**引数**

- `parentId`
    - アクティブの子トークンを取得する親トークンのID。
- `index`
    - 子トークンのインデックス。

**戻り値**

`Child`構造体。

#### pendingChildOf

指定した親トークンのペンディングの子トークンのうち、指定したインデックスの子トークンを取得する関数。

**引数**

- `parentId`
    - ペンディングの子トークンを取得する親トークンのID。
- `index`
    - 子トークンのインデックス。

**戻り値**

`Child`構造体。

#### nestTransferFrom

トークンを別のトークンに移動する関数。
移動先のトークンは、移動されるトークンまたはそれより下の階層の子トークンではない必要があります。

**引数**

- `from`
    - 移動されるトークンの直接の所有者のアドレス。
- `to`
    - トークンを移動する先のコレクションコントラクトのアドレス。
- `tokenId`
    - 移動されるトークンのID。
- `destinationId`
    - 移動されるトークンを受け入れるトークンのID。


:::message alert
トークンのIDが`0`にならないように注意してください。
この提案では`0`の値は、トークンや受け入れ先がNFTでないことを示すために使用されます。

具体的には、`0`はこの提案では以下のように使われています。

- トークンや受け入れ先がNFTでない場合、IDが`0`の値となります。
    - これにより、トークンや受け入れ先が存在しないことを示します。
- トークンや受け入れ先がNFTである場合、IDは`0`以外の値となります。
    - これにより、実際のトークンや受け入れ先を識別します。
つまり、IDが`0`の値は、特定の状況や条件を示すために使用されることを意味しています。
これにより、トークンや受け入れ先がNFTであるかどうかを明確に区別することができます。
:::

## 補足

この提案を設計する際に、以下の質問が考慮されました。

提案の設計に関して、次の質問が考慮されました。

#### 1. 提案の名前はどのようにするべきか？
提案の名前は、その提案の主要な特徴や目的を的確に示す必要があります。
この提案では、親トークンがネスティングを中心に制御される仕組みが導入されています。
親トークンは子トークンをネスティングし、所有するトークンをサポートすることができます。
このようなアプローチに基づいて、提案の名前に「**Parent-Centered（親中心）**」という表現が含まれました。
これにより、提案の本質的な側面がタイトルに反映され、提案の目的や特徴が明確に伝わることが期待されます。

#### 2. なぜ**EIP712**パーミットスタイルの署名を使用して子トークンを自動的に受け入れることは、この提案の一部ではないのですか？
この提案が**EIP712**パーミットスタイルの署名を使用して子トークンを自動的に受け入れる仕組みを導入していない理由は、一貫性を保つためです。
この提案は**ERC721**を拡張しており、既にトークンの承認操作に1つのトランザクションを使用しています。
この提案がトークンの取引操作に関して既に一つの方法を採用しているため、新たに別の署名方式を導入することは一貫性に欠ける可能性があります。

異なる操作に対して異なる署名方式を使用することは、トークンの操作方法を理解しやすくするためには好ましくありません。
そのため、この提案ではすでに採用されているトークンの承認操作の方法を一貫して使用することで、ユーザーにわかりやすく、操作の予測可能性を高めることを重視しています。

#### 3. なぜインデックスを使用するのですか？
この提案がインデックスを使用する理由は、ガス消費を削減するためです。
例えば、特定のトークンを受け入れるか拒否するために、そのトークンのIDを使用する代わりに、そのトークンが配列内のどの位置にあるかを示すインデックスを使用します。
これにより、配列内のトークン数に関係なく操作のコストが一定になります。

なぜインデックスを使用するのかという疑問に対する回答の一つが、ガス消費の削減です。
トークンIDを使用して特定のトークンを見つける場合、配列を1つずつ確認する必要があり、操作のコストがアクティブな子トークンや保留中の子トークンの数に応じて増加します。
一方、インデックスを使用すると、操作のコストが一定になります。
このため、トークンごとにアクティブな子トークンと保留中の子トークンのリストを維持する必要がありますが、リストのサイズが増えても操作のコストが変わらないという利点があります。

インデックスを使用することで、競合状態を避けるためにトークンの予想IDとコレクションスマートコントラクトが操作に含まれ、正しいトークンがアクセスされることが確認されます。
また、マッピングを使用してインデックスを追跡するアプローチも試されましたが、その結果、子トークンを受け入れるコストが大幅に増加しました。
そのため、この提案ではコスト増加を受け入れる用途の拡張として実装される可能性があると述べられています。
提供されたサンプル実装には、このアプローチを採用するためのフックも用意されています。

#### 4. なぜ保留中の子トークンの配列を制限する代わりにページネーションをサポートしないのですか？
この提案では、保留中の子トークンの配列を制限する理由はいくつかあります。
保留中の子トークンの配列は、親トークンのルート所有者がアクティブな子トークンに昇格させる予定のないが、まだ保持したいトークンを一時的に収集するためのものです。
このため、保留中の子トークンの配列は長期間保持されるべきではなく、定期的にメンテナンスされる必要があります。

一方、ページネーションは通常、大量のデータを小さなページに分割して表示する場合に使用されます。
しかし、保留中の子トークンの配列は、少数のトークンが含まれることが想定されており、ページネーションのメリットを享受するほどのデータ量が存在しません。
したがって、ページネーションをサポートする必要性はありません。

また、保留中の子トークンの配列を制限することにより、スパムや悪戯から保護することも狙っています。
制限された配列内には、本来のトークン以外のスパムや悪意のあるトークンが入り込むことが難しくなります。
これにより、保留中の子トークンの配列が洪水のようなスパムトークンで埋め尽くされて、正当なトークンが見失われるリスクを軽減できます。

保留中の子トークンの配列をクリアする際に正当なトークンが誤って拒否されないようにするために、`clear pending child tokens`配列の呼び出しにはペンディングの承認を拒否する最大値が引数に追加されました。
これにより、意図しないトークンが拒否されるリスクを最小限に抑えることができます。

#### 5. トークンを自身の子トークンの1つにネストすることを許可すべきですか？
この提案では、トークンを自身の子トークンの1つにネストすることは許可されていません。
この制約は、トークンの所有権と階層構造を正しく維持するために導入されています。

例えば、親トークンAがあるとします。
このトークンAはルート所有者によって管理されており、その下に子トークンBが存在します。
もしトークンBが自身の子トークンの1つにネストされることを許可すると、トークンAとBの間に所有権のループが生じます。
つまり、トークンAはトークンBを所有し、同時にトークンBはトークンAを所有する状態となります。

このループが存在すると、トークンの所有権と管理が複雑になり、操作やトランザクションの一貫性が損なわれる可能性があります。
そのため、この提案ではトークンが自身の子トークンにネストされることを禁止しており、階層構造と所有権の健全さを保つための措置としています。

#### 6. なぜ「安全な」ネスト転送メソッドが存在しないのですか？
`nestTransfer`は常に「**安全**」です。
なぜなら、宛先で**IERC6059**の互換性をチェックする必要があるからです。

#### 7. この提案は他の類似の問題を解決しようとする他の提案とどのように異なるのですか？
この提案は、他の類似の問題を解決しようとする他の提案といくつかの点で異なります。

まず、この提案のインターフェースは、トークンを送信および受信する機能を提供します。
これにより、トークン間の相互作用が可能となり、トークン同士の移動や組み合わせが容易に行えます。

提案と承認のパターンおよび親による管理パターンは、より安全な使用を可能にします。
つまり、トークンの操作や移動には特定の手続きと許可が必要とされ、権限のある利用者によって管理されます。
これにより、トークンのセキュリティと一貫性が確保されます。

他の点として、この提案は後方互換性を重視しており、既存の**ERC721**に対して簡潔なインターフェースを提供します。
また、異なるNFTコレクション間での相互運用も可能であり、複数のスマートコントラクト間でネスティングが行えます。
これにより、異なるトークンコレクションが連携して動作することができるようになります。

総じて、この提案はトークンの送受信機能やセキュアな操作、異なるコレクション間での連携など、多くの面で他の提案とは異なるアプローチを提供しています。

### 子トークンの管理においての「提案-確定」パターン
子トークンの管理においての「**提案-確定**」パターンは、親トークンに新しい子トークンを追加する際の手順です。
このパターンは、他の第三者による制限付きの変更を許容するために使用されます。
具体的な流れは以下の通りです。

1. 子トークンの追加提案（Propose）
新しい子トークンを親トークンに追加したい場合、まずその子トークンは「**保留中**」の状態に置かれます。
これにより、まだ確定されていない提案の子トークンがトークンの配列に含まれます。

2. 子トークンの確定（Commit）
提案された子トークンは、親トークンのルート所有者によって「**保留中**」から「**アクティブ**」な状態に移動されます。
この確定された子トークンは、親トークンの管理下にある有効な子トークンとなります。

「**保留中**」の子トークンの配列は、スパムや悪意のある行動を防ぐために`128`個までに制限されています。
これにより、不正なトークンの大量発行などを防ぐことができます。

ルート所有者のみが子トークンを受け入れることができるという制約は、提案全体における信頼性を確保するためのものです。
これにより、トークンのルート所有者がトークンに対する完全な制御を持つことが保証されます。
ユーザーは、自分の意志に反する子トークンを受け入れることを強制されることはありません。

### 親による管理パターン
親による管理パターンは、ネストされたトークンの関連性と所有権を管理するための手法です。
以下にその内容をわかりやすく説明します。

#### 1. 所有権の概念
親NFT（親の非代替トークン）とその親のルート所有者は、ネストされたトークンの真の所有者です。
トークンを他のトークンに送信すると、送信元はそのトークンの所有権を放棄します。

#### 2. ownerOf機能の利用
**ERC721**の仕様に基づいて、ネストされたトークンは所有権をたどることができます。
このため、トークンは親をたどり、NFTでないアドレス（ルート所有者と呼ばれます）を見つけることができます。

#### 3. directOwnerOfメソッド
さらに、提案では`directOwnerOf`メソッドが提供されています。
このメソッドは、トークンの直接の所有者情報を返します。
具体的には、所有者のアドレス、直接の所有者がNFTでない場合は`0`である必要があるトークンID、および親がNFTであるかどうかを示すフラグが含まれます。

#### 4. 操作の許可
ルート所有者または承認された当事者は、子トークンに対して以下の操作を実行できるようになります。
- `acceptChild`
    - 子トークンの受け入れ。
- `rejectAllChildren`
    - すべての子トークンの拒否。
- `transferChild`
    - 子トークンの転送。

#### 5. NFTでない場合の操作
トークンがNFTでない場合、ルート所有者または承認された当事者は以下の操作を実行できるようになります。
- `transferFrom`
    - トークンの転送。
- `safeTransferFrom`
    - 安全なトークンの転送。
- `nestTransferFrom`
    - ネストされたトークンの転送。
- `burn`
    - トークンの焼却。

#### 6. NFTで所有されている場合の操作
トークンがNFTで所有されている場合、これらの操作は親NFT自体だけが実行できます。
転送は親トークンから行われ、この方法が他のトークンに対して適切な操作を実行するための入り口となります。

この制限は、トークンが親から転送される際に、子トークンを親から適切に取り除くために導入されました。
これにより、親コントラクト内の不整合が防止されます。

### 子トークンの管理
この提案では、いくつかの子トークンの管理機能が導入されています。
子トークンを「**保留中**」から「**アクティブ**」な子トークンの配列に許可された移行することに加えて、この提案の主要なトークン管理機能は、`transferChild`関数です。
この関数によって子トークンの以下の状態遷移が可能です。

- 子トークンの拒否
- 子トークンの放棄
- 子トークンのネスト解除
- 子トークンのEOAまたは**ERC721Receiver**への転送
- 子トークンを新しい親トークンに転送

これらの状態遷移がどのように実現されるかを理解するために、`transferChild`に渡される利用可能なパラメータを見てみましょう：

```solidity
function transferChild(
    uint256 tokenId,
    address to,
    uint256 destinationId,
    uint256 childIndex,
    address childAddress,
    uint256 childId,
    bool isPending,
    bytes data
) external;
```

希望する状態遷移に基づいて、これらのパラメータの値を適切に設定する必要があります（以下の例では設定されていないパラメータは、管理されている子トークンに依存します）。

#### Reject child token

![eip-6059-reject-child.png](/images/qiita-e5ccc190-16f8-d885-c4cc-f05b5601e102.webp)

#### Abandon child token

![eip-6059-abandon-child.png](/images/qiita-ae04f53e-8a8f-e305-91e9-051ef9d42c4b.webp)

#### Unnest child token

![eip-6059-unnest-child.png](/images/qiita-492a28eb-6892-27b3-8f8f-a75e26f69288.webp)

#### Transfer the child token to an EOA or an ERC721Receiver

![eip-6059-transfer-child-to-eoa.png](/images/qiita-3fdd83c7-04a1-7ed1-96df-14b025faf742.webp)

#### Transfer the child token into a new parent token
 
![eip-6059-transfer-child-to-token.png](/images/qiita-791915d0-32fe-3cb8-3d9f-916916ba86c8.webp)

この状態変更は、トークンを新しい親トークンの保留中の配列に配置します。
しかし、子トークンはまだ新しい親トークンのルート所有者によって受け入れられる必要があります。
その後、子トークンはそのトークンのアクティブな配列に配置されます。

## 後方互換性
「**ネスタブルトークン**」規格は、**ERC721**と互換性があるように設計されています。
これにより、**ERC721**の実装に利用可能な堅牢なツール類を活用できるだけでなく、既存の**ERC721**のインフラストラクチャとの互換性も確保されます。

## テスト

テストファイルは以下に格納されています。

https://github.com/cardene777/base-code/blob/develop/contract/test/ERC6059/nestable.ts

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

## 参考実装

以下に参考実装をまとめています。

https://github.com/cardene777/base-code/blob/develop/contract/contracts/ERC6059/NestableToken.sol

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

## セキュリティ考慮事項
**ERC721**と同様のセキュリティ上の注意事項が適用されます。
バーン（焼却）、子トークンの追加、子トークンの受け入れなど、すべての関数において隠されたロジックが存在する可能性があります。

トークンの現在の所有者はトークンを管理することが許可されているため、親トークンが販売リストに掲載された後、売り手が販売前に子トークンを削除する可能性があります。
したがって、買い手は期待していた子トークンを受け取らない可能性があります。
これはこの規格の設計上のリスクです。
マーケットプレイスはこれを考慮に入れ、親トークンが販売される際に期待される子トークンが存在することを確認する方法を提供するか、その他の方法で悪意のある行動に対処する必要があります。

## 引用

Bruno Škvorc (@Swader), Cicada (@CicadaNCR), Steven Pineda (@steven2308), Stevan Bogosavljevic (@stevyhacker), Jan Turk (@ThunderDeliverer), "ERC-6059: Parent-Governed Nestable Non-Fungible Tokens," Ethereum Improvement Proposals, no. 6059, November 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-6059.

## 考察

複数のNFTを使用してネスト構造をもたらすのは面白い提案だなと思いました。

最初見た時は以下の**ERC6150**と同じでは？と思ったのですが、親子関係のNFTが異なるコントラクトアドレスである点が大きな違いなのかなと思いました。

[\[ERC6150\] NFTにLinuxのファイルシステムのような階層構造を持たせる仕組みを理解しよう！](/articles/38c75ae824373684aecd)

複数のコントラクト内のNFTを管理するため、しっかりと全体像を把握してないとカオスな状態になる可能性はありつつ、色々と面白いことができそうな規格です。

## 最後に

今回は「ネストされたNFTのインターフェースを提供して、NFTに親子関係をもたらす提案である**ERC6059**」についてまとめてきました！
いかがだったでしょうか？

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

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

## 採用強化中！

CryptoGamesでは一緒に働く仲間を大募集中です。

https://www.wantedly.com/companies/cryptogames

この記事で書いた自分の経験からもわかるように、裁量権を持って働くことができて一気に成長できる環境です。
「**ブロックチェーンやWeb3、NFTに興味がある**」、「**スマートコントラクトの開発に携わりたい**」など、少しでも興味を持っている方はまずはお話ししましょう！
