はじめに

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

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

今回は、NFTを別のNFTにネストする規格であるERC6059の課題点を解決した提案であるERC7401についてまとめていきます!

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

7401は現在(2023年9月12日)では「Last Call」段階です。

概要

ERC7401は、ERC6059を置き換える新しい規格です。 これは、NFT(非代替可能トークン)の親子関係を管理するためのもので、ERC721規格を拡張しています。

ERC6059については以下を参考にしてください。

この提案の基本的なアイデアは次のとおりです。

「NFTの所有者は、従来の外部所有アカウント(EOA)やスマートコントラクトだけでなく、別のNFTであることもできます。つまり、NFT自体が他のNFTを所有できるのです。」

NFTを別のNFTにネスト(入れ子にする)するプロセスは、単純にNFTを別のユーザーに送信するプロセスと同じように機能します。 ただし、親NFTから子NFTにトークンを送信する場合、親NFTを所有するアカウントからトランザクションを発行する必要があります。

NFTは他のNFTによって所有されることができますし、逆に1つのNFTが複数のNFTを所有することもできます。 この提案は、NFT間の親子関係を確立するための枠組みを提供しています。 親NFTは他のNFTを所有しているトークンで、子NFTは他のNFTによって所有されているトークンです。 そして、同じトークンが親と子の両方であることも可能です。 子NFTは親NFTの所有者によって完全に管理できますが、新しい関連性を提案することも誰でも行えます。

この規格は、NFTの所有と関係をより柔軟に管理するためのもので、NFTエコシステムの発展に寄与することが期待されています。

eip-7401-nestable-tokens.png

グラフは、1つのトークンが同時に親トークンと子トークンである状況を示しています。

このトークンは他のトークンを所有しており、同時に他のトークンによって所有されています。 この親トークンの所有者は、すべての子トークンとその関連トークンに対して最終的な権限を持っており、それらのトークンを自由に管理できます。 この仕組みにより、NFTの所有構造を非常に柔軟に管理でき、複雑な所有関係を効果的に表現できます。

動機

NFT(非代替可能トークン)は、Ethereumエコシステムで広く使用され、さまざまな目的で活用されています。 このため、NFTに追加の機能を統一規格として定義する必要が生じました。 トークンが他のトークンを所有できる機能を持たせることは、これらのトークンの有用性、使いやすさ、そして将来の互換性を向上させることができる重要な要素となります。

ERC721が最初に導入されてから4年が経過し、NFTに対するさまざまな要望やニーズが出てきました。 この新たな規格(ERC)は、ERC721を以下の4つの領域で改善しています。

  • バンドリング(Bundling)
    • トークンをまとめて管理する機能。
  • コレクティング(Collecting)
    • トークンを収集する機能。
  • メンバーシップ(Membership)
    • トークンが特定のグループやコミュニティに所属できる機能。
  • デリゲーション(Delegation)
    • トークンの所有権や機能を他のユーザーに委任できる機能。

また、この提案はERC6059のインターフェース仕様にあった一貫性の問題を修正しています。 ERC6059の仕様には、提案の進行に伴いインターフェースIDが合わないという不一致があったため、この問題を修正しました。 しかし、修正以外の点では、この提案は機能的にはERC6059と同等です。

バンドリング(Bundling)

ERC721は、マルチメディアコンテンツと結びついたトークンを広めるためによく使われています。 複数のコレクションからNFTを束ねて、1つの取引として販売する方法は、現在容易ではありませんでした。 この提案は、このプロセスを標準化する方法を提供します。 すべてのトークンを単一のバンドルにまとめ、それを販売することで、すべてのトークンの制御権を1つのトランザクションで買い手に移譲できます。

コレクティング(Collecting)

多くのNFTコレクターは、さまざまな基準に基づいてトークンを収集しています。 一部の人はトークンの実用性、他の人はユニークさ、ビジュアルの魅力などに注目します。 特定のアカウントに関連するNFTをグループ化する標準的な方法は存在しませんでした。 この提案は、所有者の好みに応じてNFTをまとめる機能を導入します。 ルートの親トークンは特定のトークングループを表し、それにネストされた子トークンはそのグループに属します。

また、この提案は、ソウルバウンド(譲渡不可)トークンの台頭に対応するためのものです。 複数のソウルバウンドトークン(子トークン)を持つことで、さまざまな用途が考えられます。 例えば、供給チェーンのユースケースでは、輸送コンテナを表すNFTがあり、その旅の各段階を示すために複数の子トークンが存在できます。

メンバーシップ(Membership)

NFTには分散型自治組織(DAO)や閉鎖的なグループへのメンバーシップが関連していることがあります。 一部の組織は、メンバーシップのNFT保持者に対してNFTを発行します。 トークンをトークンにネストする機能を使用することで、発行プロセスを簡素化し、ボーナスNFTをメンバーシップNFTに直接発行できるようになります。

デリゲーション(Delegation)

DAOの主要な機能の1つは投票であり、さまざまなアプローチがあります。 その中の1つは、メンバーが他のメンバーに投票権を委任する仕組みです。 この提案を使用すると、委任投票を行う際に、自分の投票NFTを委任先のNFTにネストし、メンバーが委任を解除する際にそれを元の所有者に送ることができます。

仕様

javascript
/// @title EIP-7401 Parent-Governed Nestable Non-Fungible Tokens
/// @dev See https://eips.ethereum.org/EIPS/eip-7401
/// @dev Note: the ERC-165 identifier for this interface is 0x42b0e56f.

pragma solidity ^0.8.16;

interface IERC7059 /* is ERC165 */ {
    /**
     * @notice The core struct of ownership.
     * @dev The `DirectOwner` struct is used to store information of the next immediate owner, be it the parent token,
     * an `ERC721Receiver` contract or an externally owned account.
     * @dev If the token is not owned by an NFT, the `tokenId` MUST equal `0`.
     * @param tokenId ID of the parent token
     * @param ownerAddress Address of the owner of the token. If the owner is another token, then the address MUST be
     *  the one of the parent token's collection smart contract. If the owner is externally owned account, the address
     *  MUST be the address of this account
     */
    struct DirectOwner {
        uint256 tokenId;
        address ownerAddress;
    }

    /**
     * @notice The core child token struct, holding the information about the child tokens.
     * @return tokenId ID of the child token in the child token's collection smart contract
     * @return contractAddress Address of the child token's smart contract
     */
    struct Child {
        uint256 tokenId;
        address contractAddress;
    }

    /**
     * @notice Used to notify listeners that the token is being transferred.
     * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
     * @param from Address of the previous immediate owner, which is a smart contract if the token was nested.
     * @param to Address of the new immediate owner, which is a smart contract if the token is being nested.
     * @param fromTokenId ID of the previous parent token. If the token was not nested before, the value MUST be `0`
     * @param toTokenId ID of the new parent token. If the token is not being nested, the value MUST be `0`
     * @param tokenId ID of the token being transferred
     */
    event NestTransfer(
        address indexed from,
        address indexed to,
        uint256 fromTokenId,
        uint256 toTokenId,
        uint256 indexed tokenId
    );

    /**
     * @notice Used to notify listeners that a new token has been added to a given token's pending children array.
     * @dev Emitted when a child NFT is added to a token's pending array.
     * @param tokenId ID of the token that received a new pending child token
     * @param childIndex Index of the proposed child token in the parent token's pending children array
     * @param childAddress Address of the proposed child token's collection smart contract
     * @param childId ID of the child token in the child token's collection smart contract
     */
    event ChildProposed(
        uint256 indexed tokenId,
        uint256 childIndex,
        address indexed childAddress,
        uint256 indexed childId
    );

    /**
     * @notice Used to notify listeners that a new child token was accepted by the parent token.
     * @dev Emitted when a parent token accepts a token from its pending array, migrating it to the active array.
     * @param tokenId ID of the token that accepted a new child token
     * @param childIndex Index of the newly accepted child token in the parent token's active children array
     * @param childAddress Address of the child token's collection smart contract
     * @param childId ID of the child token in the child token's collection smart contract
     */
    event ChildAccepted(
        uint256 indexed tokenId,
        uint256 childIndex,
        address indexed childAddress,
        uint256 indexed childId
    );

    /**
     * @notice Used to notify listeners that all pending child tokens of a given token have been rejected.
     * @dev Emitted when a token removes all a child tokens from its pending array.
     * @param tokenId ID of the token that rejected all of the pending children
     */
    event AllChildrenRejected(uint256 indexed tokenId);

    /**
     * @notice Used to notify listeners a child token has been transferred from parent token.
     * @dev Emitted when a token transfers a child from itself, transferring ownership.
     * @param tokenId ID of the token that transferred a child token
     * @param childIndex Index of a child in the array from which it is being transferred
     * @param childAddress Address of the child token's collection smart contract
     * @param childId ID of the child token in the child token's collection smart contract
     * @param fromPending A boolean value signifying whether the token was in the pending child tokens array (`true`) or
     *  in the active child tokens array (`false`)
     * @param toZero A boolean value signifying whether the token is being transferred to the `0x0` address (`true`) or
     *  not (`false`)
     */
    event ChildTransferred(
        uint256 indexed tokenId,
        uint256 childIndex,
        address indexed childAddress,
        uint256 indexed childId,
        bool fromPending,
        bool toZero
    );

    /**
     * @notice Used to retrieve the *root* owner of a given token.
     * @dev The *root* owner of the token is the top-level owner in the hierarchy which is not an NFT.
     * @dev If the token is owned by another NFT, it MUST recursively look up the parent's root owner.
     * @param tokenId ID of the token for which the *root* owner has been retrieved
     * @return owner The *root* owner of the token
     */
    function ownerOf(uint256 tokenId) external view returns (address owner);

    /**
     * @notice Used to retrieve the immediate owner of the given token.
     * @dev If the immediate owner is another token, the address returned, MUST be the one of the parent token's
     *  collection smart contract.
     * @param tokenId ID of the token for which the direct owner is being retrieved
     * @return address Address of the given token's owner
     * @return uint256 The ID of the parent token. MUST be `0` if the owner is not an NFT
     * @return bool The boolean value signifying whether the owner is an NFT or not
     */
    function directOwnerOf(uint256 tokenId)
        external
        view
        returns (
            address,
            uint256,
            bool
        );

    /**
     * @notice Used to burn a given token.
     * @dev When a token is burned, all of its child tokens are recursively burned as well.
     * @dev When specifying the maximum recursive burns, the execution MUST be reverted if there are more children to be
     *  burned.
     * @dev Setting the `maxRecursiveBurn` value to 0 SHOULD only attempt to burn the specified token and MUST revert if
     *  there are any child tokens present.
     * @param tokenId ID of the token to burn
     * @param maxRecursiveBurns Maximum number of tokens to recursively burn
     * @return uint256 Number of recursively burned children
     */
    function burn(uint256 tokenId, uint256 maxRecursiveBurns)
        external
        returns (uint256);

    /**
     * @notice Used to add a child token to a given parent token.
     * @dev This adds the child token into the given parent token's pending child tokens array.
     * @dev The destination token MUST NOT be a child token of the token being transferred or one of its downstream
     *  child tokens.
     * @dev This method MUST NOT be called directly. It MUST only be called from an instance of `IERC7059` as part of a 
        `nestTransfer` or `transferChild` to an NFT.
     * @dev Requirements:
     *
     *  - `directOwnerOf` on the child contract MUST resolve to the called contract.
     *  - the pending array of the parent contract MUST not be full.
     * @param parentId ID of the parent token to receive the new child token
     * @param childId ID of the new proposed child token
     */
    function addChild(uint256 parentId, uint256 childId) external;

    /**
     * @notice Used to accept a pending child token for a given parent token.
     * @dev This moves the child token from parent token's pending child tokens array into the active child tokens
     *  array.
     * @param parentId ID of the parent token for which the child token is being accepted
     * @param childIndex Index of the child token to accept in the pending children array of a given token
     * @param childAddress Address of the collection smart contract of the child token expected to be at the specified
     *  index
     * @param childId ID of the child token expected to be located at the specified index
     */
    function acceptChild(
        uint256 parentId,
        uint256 childIndex,
        address childAddress,
        uint256 childId
    ) external;

    /**
     * @notice Used to reject all pending children of a given parent token.
     * @dev Removes the children from the pending array mapping.
     * @dev The children's ownership structures are not updated.
     * @dev Requirements:
     *
     * - `parentId` MUST exist
     * @param parentId ID of the parent token for which to reject all of the pending tokens
     * @param maxRejections Maximum number of expected children to reject, used to prevent from
     *  rejecting children which arrive just before this operation.
     */
    function rejectAllChildren(uint256 parentId, uint256 maxRejections) external;

    /**
     * @notice Used to transfer a child token from a given parent token.
     * @dev MUST remove the child from the parent's active or pending children.
     * @dev When transferring a child token, the owner of the token MUST be set to `to`, or not updated in the event of `to`
     *  being the `0x0` address.
     * @param tokenId ID of the parent token from which the child token is being transferred
     * @param to Address to which to transfer the token to
     * @param destinationId ID of the token to receive this child token (MUST be 0 if the destination is not a token)
     * @param childIndex Index of a token we are transferring, in the array it belongs to (can be either active array or
     *  pending array)
     * @param childAddress Address of the child token's collection smart contract
     * @param childId ID of the child token in its own collection smart contract
     * @param isPending A boolean value indicating whether the child token being transferred is in the pending array of the
     *  parent token (`true`) or in the active array (`false`)
     * @param data Additional data with no specified format, sent in call to `to`
     */
    function transferChild(
        uint256 tokenId,
        address to,
        uint256 destinationId,
        uint256 childIndex,
        address childAddress,
        uint256 childId,
        bool isPending,
        bytes data
    ) external;

    /**
     * @notice Used to retrieve the active child tokens of a given parent token.
     * @dev Returns array of Child structs existing for parent token.
     * @dev The Child struct consists of the following values:
     *  [
     *      tokenId,
     *      contractAddress
     *  ]
     * @param parentId ID of the parent token for which to retrieve the active child tokens
     * @return struct[] An array of Child structs containing the parent token's active child tokens
     */
    function childrenOf(uint256 parentId)
        external
        view
        returns (Child[] memory);

    /**
     * @notice Used to retrieve the pending child tokens of a given parent token.
     * @dev Returns array of pending Child structs existing for given parent.
     * @dev The Child struct consists of the following values:
     *  [
     *      tokenId,
     *      contractAddress
     *  ]
     * @param parentId ID of the parent token for which to retrieve the pending child tokens
     * @return struct[] An array of Child structs containing the parent token's pending child tokens
     */
    function pendingChildrenOf(uint256 parentId)
        external
        view
        returns (Child[] memory);

    /**
     * @notice Used to retrieve a specific active child token for a given parent token.
     * @dev Returns a single Child struct locating at `index` of parent token's active child tokens array.
     * @dev The Child struct consists of the following values:
     *  [
     *      tokenId,
     *      contractAddress
     *  ]
     * @param parentId ID of the parent token for which the child is being retrieved
     * @param index Index of the child token in the parent token's active child tokens array
     * @return struct A Child struct containing data about the specified child
     */
    function childOf(uint256 parentId, uint256 index)
        external
        view
        returns (Child memory);

    /**
     * @notice Used to retrieve a specific pending child token from a given parent token.
     * @dev Returns a single Child struct locating at `index` of parent token's active child tokens array.
     * @dev The Child struct consists of the following values:
     *  [
     *      tokenId,
     *      contractAddress
     *  ]
     * @param parentId ID of the parent token for which the pending child token is being retrieved
     * @param index Index of the child token in the parent token's pending child tokens array
     * @return struct A Child struct containing data about the specified child
     */
    function pendingChildOf(uint256 parentId, uint256 index)
        external
        view
        returns (Child memory);

    /**
     * @notice Used to transfer the token into another token.
     * @dev The destination token MUST NOT be a child token of the token being transferred or one of its downstream
     *  child tokens.
     * @param from Address of the direct owner of the token to be transferred
     * @param to Address of the receiving token's collection smart contract
     * @param tokenId ID of the token being transferred
     * @param destinationId ID of the token to receive the token being transferred
     * @param data Additional data with no specified format
     */
    function nestTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        uint256 destinationId,
        bytes memory data
    ) external;
}

IDは決して0であってはいけないです。 なぜなら、本提案ではtoken/destinationがNFTでないことを示すために0を使用するからです。

補足

この提案の設計に際して、以下の質問を考慮しました。

提案の名前はなぜこのようになったのですか?

この提案の最も重要な側面は、親トークンがネスティングを中心に制御することです。 子トークンの役割は、ネスティング可能で、それを所有するトークンをサポートすることだけです。 このため、「Parent-Centered(親中心)」のタイトルを選びました。

なぜEIP-712の許可スタイルの署名を使用して子トークンを自動的に受け入れないのですか?

一貫性を保つためです。 この提案はERC721を拡張しており、ERC721は既にトークンの操作を承認するために1つのトランザクションを使用しています。 メッセージ署名をサポートすることと同時にこれをサポートすることは一貫性が欠けるためです。

なぜインデックスを使用するのですか?

ガス消費を削減するためです。 もしトークンIDを使用して特定のトークンを見つけようとする場合、配列の反復が必要で、操作のコストはアクティブまたは保留中の子トークンの配列のサイズに依存します。 インデックスを使用することで、コストが一定になります。 アクティブおよび保留中の子トークンのリストを維持する必要があるため、これらのリストを取得するためのメソッドは提案されたインターフェースの一部です。

トークンのインデックスが変更される競合状態を回避するため、トークンのインデックスを使用する操作には、期待されるトークンIDと期待されるトークンのコレクションスマートコントラクトも含まれており、インデックスを使用してアクセスされるトークンが期待通りであることを確認します。

マッピングを使用して内部的にインデックスを追跡する実装を試みましたが、子トークンを受け入れる最小コストが20%以上増加し、mintのコストも15%以上増加しました。 この提案ではこれは必要ないと結論づけ、トランザクションコストの増加を受け入れる用途向けの拡張として実装できるようにしました。 提供されたサンプル実装にはこれを可能にするいくつかのフックが含まれています。

保留中の子トークンの配列を制限する理由は何ですか?

保留中の子トークンの配列は、親トークンのルート所有者が保持するトークンを集めるためのバッファではなく、アクティブの子トークンに昇格させるために不足しているトークンを収集するためのものではないです。 これは単に子トークン候補のリストであり、定期的に保守する必要があります。 保留中の子トークンの配列が無制限である必要はありません。 制限のある保留中の子トークンの配列は、スパムや妨害から保護する役割も果たします。 悪意のあるスパムトークンを発行することは比較的容易で低コストで行えるため、制限された保留中のトークンの配列により、それらのトークンは簡単に特定でき、正当なトークンが大量のスパムトークンの中に埋もれないようになります。

また、保留中の子トークンの配列をクリアする際に、誤って正当なトークンが拒否されないようにする方法も考慮されました。 これに関連して、最大保留中の子トークンを拒否する引数がクリア保留中の子トークン配列呼び出しに追加されました。 これにより、意図された数の保留中の子トークンのみが拒否され、クリア中に新しいトークンが保留中の子トークン配列に追加された場合でも、この配列のクリアはrevertされるべきであることが保証されます。

トークンをその子の1つにネストすることを許可すべきかどうか

この提案は、親トークンがその子トークンまたはそれ以下の子トークンのいずれかにネストされないように制約しています。 親トークンとその子トークンはすべて親トークンのルート所有者によって管理されます。 つまり、トークンがその子トークンの1つにネストされた場合、これは所有権のループを作成し、ループ内のトークンはもはや管理できなくなります。

なぜ「安全な」ネスト転送メソッドが存在しないのですか?

nestTransferは常に「安全」です。 なぜなら、nestTransferは送付先でIERC7059との互換性を確認する必要があるからです。

この提案は、類似の問題に対処しようとする他の提案とどう異なりますか?

このインターフェースはトークンを送信および受信することを許可します。 提案-受け入れおよび親が統治するパターンは、より安全な使用を可能にします。 後方互換性はERC721にのみ追加され、よりシンプルなインターフェースが提供されます。 また、この提案は異なるコレクション間での相互運用性も提供し、ネスティングが単一のスマートコントラクトに制限されず、完全に別のNFTコレクション間で実行できるようにします。 さらに、この提案はERC6059のインターフェースID、インターフェース仕様、およびサンプル実装の一貫性の問題にも対処しています。

提案-コミットパターン(Propose-Commit pattern)

子トークンを親トークンに追加する場合、まずは第三者による制限付きの変更を可能にするため、「提案-コミットパターン」を使用します。 具体的には、子トークンは最初に「保留中」の配列に配置され、その後、親トークンのルート所有者によって「*8アクティブ」の配列に移動されます。 この「保留中**」の子トークンの配列は、スパムや妨害を防ぐために最大128のスロットに制限されます。

また、子トークンを受け入れるのはルート所有者のみが許可されるため、トークンのルート所有者がトークンを完全に制御でき、ユーザーが子トークンを受け入れることを強制することはできません。

親が統治するパターン(Parent Governed pattern)

このパターンでは、ネストされたトークンの親NFTとその親のルート所有者がトークンの真の所有者であるとされます。 トークンを別のトークンに送信すると、所有権は移転します。

このパターンでは、ERC721のownerOf機能が使用され、親を遡って所有者を検索します。 そして、最終的なルート所有者が見つかります。 さらに、トークンの最も直接の所有者を返すためのdirectOwnerOfも提供されます。 このメソッドは、所有者のアドレス、tokenId(直接の所有者がNFTでない場合は0)、親がNFTであるかどうかを示すフラグを返します。

親が統治するパターンでは、ルート所有者または承認された第三者が子トークンに対して特定の操作を実行できるようになります。 これには、子トークンの受け入れ、すべての子トークンの拒否、子トークンの転送などが含まれます。

ただし、トークンがNFTによって所有されている場合、上記の操作を実行できるのは親NFT自体だけです。 トークンの転送は親トークンから行われ、このメソッドは子トークンのスマートコントラクト内で、宛先がNFTかどうかに応じて適切な転送メソッドを呼び出します。バーニング(焼却)の場合、トークンはまずEOA(外部所有者アカウント)に転送され、その後焼却されます。

これらの制限は、子トークンが親から転送される際に、子トークンを親から正しく削除するためにtransferChildメソッドが必要であるために導入されています。 これにより、親コントラクト内での整合性の維持が確保されます。

子トークンの管理

この提案では、子トークンの管理に関連するさまざまな機能が導入されています。 中でも、transferChild関数は主要なトークン管理機能です。 この関数を使用することで、子トークンに関するさまざまな操作と状態遷移が可能になります。 以下はtransferChild関数を通じて実現できる子トークンの状態遷移の例です。

  • 子トークンの拒否
    • 子トークンを受け入れずに拒否することができます。
  • 子トークンの放棄
    • 所有権を放棄し、子トークンを所有者から外すことができます。
  • 子トークンのネスティング解除
    • 子トークンがネストされた親トークンから解除され、単独で存在できるようになります。
  • 子トークンをEOAまたはERC721Receiverに転送
    • 子トークンを外部所有者アカウント(EOA)やERC721ERC721Receiverアドレスに転送できます。
  • 子トークンを新しい親トークンに転送
    • 子トークンを別の親トークンに転送し、ネストさせることができます。

これらの操作を実行するには、transferChild関数に渡すパラメータを適切に設定する必要があります。 以下はtransferChild関数のパラメータと説明です。

javascript
function transferChild(
    uint256 tokenId,
    address to,
    uint256 destinationId,
    uint256 childIndex,
    address childAddress,
    uint256 childId,
    bool isPending,
    bytes data
) external;
  • tokenId
    • 子トークンの一意の識別子。
  • to
    • 子トークンを転送する先のアドレス。
    • これは外部所有者アカウント(EOA)やERC721Receiverのアドレスです。
  • destinationId
    • 子トークンを転送する新しい親トークンの識別子。
  • childIndex
    • 子トークンが親トークン内での位置を示すインデックス。
  • childAddress
    • 子トークンのスマートコントラクトアドレス。
  • childId
    • 子トークンの一意の識別子。
  • isPending
    • 子トークンが保留中の状態かどうかを示すブール値。
  • data
    • 追加のデータや情報を指定するためのパラメータ。

これらのパラメータを適切に設定することで、子トークンの望む状態遷移を実現できます。 たとえば、子トークンを新しい親トークンにネストさせる場合、destinationIdパラメータに新しい親トークンの識別子を設定します。 同様に、子トークンを別のアドレスに転送する場合、toパラメータに転送先のアドレスを指定します。

  1. 子トークンの拒否 (Reject child token) この操作では、子トークンを受け入れず、現在の親トークンから子トークンを排除します。 子トークンは新しい親トークンに移行しません。

eip-7401-reject-child.png

  1. 子トークンの放棄 (Abandon child token) この操作では、所有者が子トークンの所有権を放棄し、子トークンを所有者から外します。 子トークンは自立して存在できるようになります。

eip-7401-abandon-child.png

  1. 子トークンのネスティング解除 (Unnest child token) この操作では、子トークンが現在の親トークンから取り外され、単独で存在できるようになります。 ネスティング(親子関係)が解消されます。

eip-7401-unnest-child.png

  1. 子トークンをEOAまたはERC721Receiverに転送 (Transfer the child token to an EOA or an ERC721Receiver) この操作では、子トークンを外部所有者アカウント(EOA)またはERC721Receiverに転送できます。 子トークンは新しい所有者に移行します。

eip-7401-transfer-child-to-eoa.png

  1. 子トークンを新しい親トークンに転送 (Transfer the child token into a new parent token) この操作は、子トークンを新しい親トークンに転送するものです。 子トークンは新しい親トークンの「保留中」の配列に配置されます。 ただし、子トークンは新しい親トークンのルート所有者によって受け入れられる必要があり、受け入れられた場合に初めて「アクティブ」の配列に移行します。

eip-7401-transfer-child-to-token.png

後方互換性

ネスタブルトークン規格は、ERC721という他のトークン規格との互換性を確保するために設計されました。 これは、ERC721に関連する既存のツールやプラットフォームを活用し、ネスタブルトークンを他のトークンとも連携できるようします。

唯一の違いは、ネスタブルトークンがトークンIDとして0を使用できないことです。 ERC721では、トークンIDとして0を使用することができますが、ネスタブルトークンではこの制限があります。

また、所有権を調べるためのownerOfメソッドの動作にも違いがあります。 通常のERC721では、ownerOfメソッドは与えられたトークンの所有者を返しますが、ネスタブルトークンでは所有権を再帰的に追跡します。 つまり、親トークンから始めて、親トークンの所有者を調べ、それを繰り返して最終的な所有者(ルート所有者)を見つけます。 これにより、トークンがネストされている場合でも、正確な所有者を特定できます。

さらに、ネスタブルトークンでは直接の所有者を示すdirectOwnerOfメソッドも提供されています。このメソッドは、トークンの所有者のアドレス、トークンID、および親がNFTかどうかを示すフラグを返します。 この情報は、より詳細な所有者情報を取得するために役立ちます。

ネスタブルトークン規格は、これらの仕様を通じてERC721と互換性を維持しながら、ネスタブルトークン独自の機能を提供します。 これにより、既存のERC-721関連のアプリケーションやツールとスムーズに連携できるようになり、ユーザーエクスペリエンスが向上します。

テスト

以下のGithubにコードを格納しています。

参考実装

以下のGithubにコードを格納しています。


_MAX_LEVELS_TO_CHECK_FOR_INHERITANCE_LOOP

javascript
uint256 private constant _MAX_LEVELS_TO_CHECK_FOR_INHERITANCE_LOOP = 100;

概要 トークンの相互継承のループをチェックする時の最大レベルを制限する変数。

詳細

  • 相互継承のループを防ぐためのセーフガードです。
  • トークン同士が互いに所有権を持つ場合、無限ループを防ぐ役割を果たします。
  • この変数の値は100で、100回以上の相互継承のチェックを防ぎます。

_balances

javascript
mapping(address => uint256) private _balances;

概要 各所有者のトークン数を追跡する配列。

詳細

  • 各ユーザー(アドレス)が所有するトークン数を格納します。
  • 各所有者のアドレスがキーで、その所有者が保有するトークン数が値として格納されます。

パラメータ

  • address
    • トークンの所有者のアドレス。
  • uint256
    • 所有者が保有するトークン数。

_tokenApprovals

javascript
mapping(uint256 => mapping(address => address)) private _tokenApprovals;

概要 トークンの所有者が承認したアドレスを追跡し、トークンの移転に関する権限を管理する配列。

詳細

  • トークンIDごとに所有者が承認したアドレスを格納します。
  • トークンIDをキーとし、所有者が承認したアドレス(承認者)が値として格納されます。
  • トークンの移転において、承認者のアドレスが有効でなければならず、この情報は子トークンの移転時に特に重要です。

パラメータ

  • uint256
    • トークンの一意の識別子(トークンID)。
  • address
    • 承認者のアドレス。
  • address
    • 承認されたアドレス。

_operatorApprovals

javascript
mapping(address => mapping(address => bool)) private _operatorApprovals;

概要 所有者がオペレーターに対して実行権限を付与しているか管理する配列。

詳細

  • 所有者がオペレーター(外部アドレス)に対して実行権限を付与しているかを格納します。
  • 所有者のアドレスがキーとなり、オペレーターのアドレスとその実行権限の状態(trueまたはfalse)が値として格納されます。
  • オペレーターは所有者の代わりにトークンを操作できる権限を持つ場合に使用されます。

パラメータ

  • address
    • トークンの所有者のアドレス。
  • address
    • オペレーターのアドレス。
  • bool
    • 実行権限の状態(trueまたはfalse)。

_directOwners

javascript
mapping(uint256 => DirectOwner) private _directOwners;

概要 トークンの直接の所有者情報を格納する配列。

詳細

  • トークンIDごとにトークンの直接の所有者情報を格納します。
  • トークンIDをキーとし、DirectOwner構造体が値として格納されます。
  • この情報はトークンの所有権階層を管理するために使用されます。

パラメータ

  • uint256
    • トークンの一意の識別子(トークンID)。
  • DirectOwner
    • トークンの直接の所有者情報(構造体)。

_activeChildren

javascript
mapping(uint256 => Child[]) internal _activeChildren;

概要 親トークンに関連付けられたアクティブな子トークンの情報を格納する配列。

詳細

  • 親トークンのトークンIDをキーとし、関連するアクティブな子トークンの情報(Child構造体)が配列として格納されます。
  • この情報は、親トークンが保持する子トークンの管理に使用されます。

パラメータ

  • uint256
    • 親トークンのトークンID。
  • Child[]
    • 関連するアクティブな子トークンの情報を格納した配列。

_pendingChildren

javascript
mapping(uint256 => Child[]) internal _pendingChildren;

概要 親トークンに関連付けられた保留中の子トークンの情報を格納する配列。

詳細

  • 親トークンのトークンIDをキーとし、関連する保留中の子トークンの情報(Child構造体)が配列として格納されます。
  • この情報は、親トークンが保留中の子トークンを管理し、アクティブな子トークンに移行させるために使用されます。

パラメータ

  • uint256
    • 親トークンのトークンID。
  • Child[]
    • 関連する保留中の子トークンの情報を格納した配列。

_childIsInActive

javascript
mapping(address => mapping(uint256 => uint256)) internal _childIsInActive;

概要 子トークンがアクティブな状態か保留中の状態かを追跡する配列。

詳細

  • 子トークンのスマートコントラクトアドレスをキーとし、子トークンのトークンIDをサブキーとして、子トークンの状態(アクティブまたは保留中を0または1で表現)を格納します。
  • この情報は、子トークンがアクティブな親トークンに関連付けられているか、保留中の親トークンに関連付けられているかを追跡するために使用されます。

パラメータ

  • address
    • 子トークンのスマートコントラクトアドレス。
  • uint256
    • 子トークンのトークンID。
  • uint256
    • 子トークンの状態(アクティブ:0、保留中:1)。

_onlyApprovedOrOwner

javascript
function _onlyApprovedOrOwner(uint256 tokenId) private view {
    if (!_isApprovedOrOwner(_msgSender(), tokenId))
        revert ERC721NotApprovedOrOwner();
}

概要 トークンの操作を実行する呼び出し元がトークンの所有者または所有者の承認を受けているか確認する修飾子。

詳細

  • 指定されたトークンIDに関連するトークンの所有者または所有者の承認を持っているかどうかをチェックします。
  • 所有者または承認者でない場合、エラーメッセージ ERC721NotApprovedOrOwnerと共にトランザクションが取り消されます。

パラメータ

  • uint256
    • チェック対象のトークンの一意の識別子(トークンID)。

onlyApprovedOrOwner

javascript
modifier onlyApprovedOrOwner(uint256 tokenId) {
    _onlyApprovedOrOwner(tokenId);
    _;
}

概要 関数が呼び出される前に_onlyApprovedOrOwner関数を呼び出し、トークンの操作を実行する呼び出し元がトークンの所有者または所有者の承認を受けているか確認する修飾子。

詳細

  • 関数が実行される前に_onlyApprovedOrOwner関数を呼び出し、所有者または承認者であることを確認します。
  • 所有者または承認者でない場合、関数の実行が中断されます。
  • 確認に成功した場合、関数が実行されます。

パラメータ

  • uint256
    • チェック対象のトークンの一意の識別子(トークンID)。

_onlyApprovedOrDirectOwner

javascript
function _onlyApprovedOrDirectOwner(uint256 tokenId) private view {
    if (!_isApprovedOrDirectOwner(_msgSender(), tokenId))
        revert NotApprovedOrDirectOwner();
}

概要 呼び出し元アドレスが指定されたトークンを管理するために承認されているか、またはそのトークンの直接の所有者であるか確認する関数。

詳細

  • 指定されたトークンIDに関連するトークンを管理する権限を持っているかどうかを確認します。
  • 所有者または承認者でない場合、エラーメッセージNotApprovedOrDirectOwnerと共にトランザクションが取り消されます。

引数

  • tokenId
    • チェック対象のトークンの一意の識別子(トークンID)。

onlyApprovedOrDirectOwner

javascript
modifier onlyApprovedOrDirectOwner(uint256 tokenId) {
    _onlyApprovedOrDirectOwner(tokenId);
    _;
}

概要 関数が呼び出される前に_onlyApprovedOrDirectOwner関数を呼び出し、トークンの操作を実行する呼び出し元がトークンの所有者または所有者の承認を受けていることを確認する修飾子。

詳細

  • 関数が実行される前に_onlyApprovedOrDirectOwner関数を呼び出し、所有者または承認者であることを確認します。
  • 所有者または承認者でない場合、関数の実行が中断されます。
  • 確認に成功した場合、関数が実行されます。

引数

  • tokenId
    • チェック対象のトークンの一意の識別子(トークンID)。

supportsInterface

javascript
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
    return
        interfaceId == type(IERC165).interfaceId ||
        interfaceId == type(IERC721).interfaceId ||
        interfaceId == type(IERC7401).interfaceId;
}

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

詳細

  • ERC165、ERC721、ERC7401のインターフェースIDがサポートされている場合、trueを返します。

引数

  • interfaceId
    • 確認するインターフェースのID。

戻り値

  • bool
    • インターフェースがサポートされている場合はtrue、それ以外はfalse。

balanceOf

javascript
function balanceOf(address owner) public view virtual returns (uint256) {
    if (owner == address(0)) revert ERC721AddressZeroIsNotaValidOwner();
    return _balances[owner];
}

概要 指定されたアドレス(所有者)が保有するトークン数を返す関数。

詳細

  • アドレスが0x0(無効なアドレス)の場合、エラーメッセージERC721AddressZeroIsNotaValidOwnerと共にエラーが発生します。

引数

  • owner
    • トークン数を取得したい所有者のアドレス。

戻り値

  • uint256
    • 指定された所有者が保有するトークンの枚数。

transferFrom

javascript


function transferFrom(
    address from,
    address to,
    uint256 tokenId
) public virtual onlyApprovedOrDirectOwner(tokenId) {
    _transfer(from, to, tokenId, "");
}

概要 指定されたトークンを所有者または承認者が指定したアドレスに転送する関数。

詳細

  • 指定されたトークンIDを持つトークンを、所有者または承認者が指定したアドレスに転送します。
  • トークンの転送には_transfer関数が使用され、追加のデータは提供されていません。

引数

  • from
    • トークンを転送する元の所有者のアドレス。
  • to
    • トークンを転送する先のアドレス。
  • tokenId
    • 転送されるトークンの一意の識別子(トークンID)。

safeTransferFrom

javascript
function safeTransferFrom(
    address from,
    address to,
    uint256 tokenId
) public virtual {
    safeTransferFrom(from, to, tokenId, "");
}

概要 指定されたトークンを所有者または承認者が指定したアドレスに安全に転送する関数。

詳細

  • 転送が成功するかどうかは、受信側がERC721Receiverを実装しているかどうかに依存します。

引数

  • from
    • トークンを転送する元の所有者のアドレス。
  • to
    • トークンを転送する先のアドレス。
  • tokenId
    • 転送されるトークンの一意の識別子(トークンID)。

safeTransferFrom (追加のデータあり)

javascript
function safeTransferFrom(
    address from,
    address to,
    uint256 tokenId,
    bytes memory data
) public virtual onlyApprovedOrDirectOwner(tokenId) {
    _safeTransfer(from, to, tokenId, data);
}

概要 指定されたトークンを所有者または承認者が指定したアドレスに安全に転送する関数。 追加のデータが提供されます。

詳細

  • 転送が成功するかどうかは、受信側がERC721Receiverを実装しているかどうかに依存します。
  • また、追加のデータを受信側に送信することができます。

引数

  • from
    • トークンを転送する元の所有者のアドレス。
  • to
    • トークンを転送する先のアドレス。
  • tokenId
    • 転送されるトークンの一意の識別子(トークンID)。
  • data
    • トークン転送時に受信側に送信される追加のデータ。

nestTransferFrom

javascript
function nestTransferFrom(
    address from,
    address to,
    uint256 tokenId,
    uint256 destinationId,
    bytes memory data
) public virtual onlyApprovedOrDirectOwner(tokenId) {

    _nestTransfer(from, to, tokenId, destinationId, data);
}

概要 指定されたトークンを別のトークンに転送する関数。

詳細

  • 指定されたトークンIDを持つトークンを、別のトークンに転送します。
  • 転送先のトークンは、転送元のトークンまたはそのトークンに紐づいた子トークンでない必要があります。
  • 転送には_nestTransfer関数が使用され、追加のデータが提供されます。

引数

  • from
    • トークンを転送する元の所有者のアドレス。
  • to
    • トークンを受け取るトークンコレクションのスマートコントラクトのアドレス。
  • tokenId
    • 転送されるトークンの一意の識別子(トークンID)。
  • destinationId
    • 転送されるトークンを受け取るトークンの識別子。
  • data
    • トークン転送時に受信側に送信される追加のデータ。

_safeTransfer

javascript
function _safeTransfer(
    address from,
    address to,
    uint256 tokenId,
    bytes memory data
) internal virtual {
    _transfer(from, to, tokenId, data);
    if (!_checkOnERC721Received(from, to, tokenId, data))
        revert ERC721TransferToNonReceiverImplementer();
}

概要 トークンを安全にfromからtoへ転送する関数。

詳細

  • この内部関数は、safeTransferFromと同等であり、トークン転送を実行するための代替メカニズム(例:署名に基づいた転送)を実装するために使用できます。
  • 転送元と転送先のアドレスが0アドレスでないことを確認します。
  • tokenIdで指定されたトークンが存在し、fromによって所有されていることを確認します。
  • もしtoがスマートコントラクトを指す場合、それは IERC721Receiver-onERC721Receivedを実装している必要があり、安全な転送時に呼び出されます。
  • Transferイベントを発行します。

引数

  • from
    • 現在の所有者のアカウントのアドレス。
  • to
    • トークンを転送する先のアドレス。
  • tokenId
    • 転送されるトークンの一意の識別子(トークンID)。
  • data
    • toへの呼び出し時に送信される形式の指定されていない追加データ。

_transfer

javascript
function _transfer(
    address from,
    address to,
    uint256 tokenId,
    bytes memory data
) internal virtual {
    (address immediateOwner, uint256 parentId, ) = directOwnerOf(tokenId);
    if (immediateOwner != from) revert ERC721TransferFromIncorrectOwner();
    if (to == address(0)) revert ERC721TransferToTheZeroAddress();

    _beforeTokenTransfer(from, to, tokenId);
    _beforeNestedTokenTransfer(from, to, parentId, 0, tokenId, data);

    _balances[from] -= 1;
    _updateOwnerAndClearApprovals(tokenId, 0, to);
    _balances[to] += 1;

    emit Transfer(from, to, tokenId);
    emit NestTransfer(from, to, parentId, 0, tokenId);

    _afterTokenTransfer(from, to, tokenId);
    _afterNestedTokenTransfer(from, to, parentId, 0, tokenId, data);
}

概要 トークンをfromからtoに転送する関数。

詳細

  • 転送元と転送先のアドレスが0アドレスでないことを確認します。
  • tokenIdで指定されたトークンがfromによって所有されていることを確認します。
  • Transferイベントを発行します。
  • transferFromとは異なり、msg.senderに制限を設けません。

引数

  • from
    • 現在の所有者のアカウントのアドレス。
  • to
    • トークンを転送する先のアドレス。
  • tokenId
    • 転送されるトークンの一意の識別子(トークンID)。
  • data
    • toへの呼び出し時に送信される形式の指定されていない追加データ。

_nestTransfer

javascript
function _nestTransfer(
    address from,
    address to,
    uint256 tokenId,
    uint256 destinationId,
    bytes memory data
) internal virtual {
    (address immediateOwner, uint256 parentId, ) = directOwnerOf(tokenId);
    if (immediateOwner != from) revert ERC721TransferFromIncorrectOwner();
    if (to == address(0)) revert ERC721TransferToTheZeroAddress();
    if (to == address(this) && tokenId == destinationId)
        revert NestableTransferToSelf();

    // Destination contract checks:
    // It seems redundant, but otherwise it would revert with no error
    if (!to.isContract()) revert IsNotContract();
    if (!IERC165(to).supportsInterface(type(IERC7401).interfaceId))
        revert NestableTransferToNonNestableImplementer();
    _checkForInheritanceLoop(tokenId, to, destinationId);

    _beforeTokenTransfer(from, to, tokenId);
    _beforeNestedTokenTransfer(
        immediateOwner,
        to,
        parentId,
        destinationId,
        tokenId,
        data
    );
    _balances[from] -= 1;
    _updateOwnerAndClearApprovals(tokenId, destinationId, to);
    _balances[to] += 1;

    // Sending to NFT:
    _sendToNFT(immediateOwner, to, parentId, destinationId, tokenId, data);
}

概要 トークンを別のトークンに転送する関数。

詳細

  • 転送元と転送先のアドレスが0アドレスでないことを確認します。
  • 転送先のトークンは、転送元のトークンまたはそのトークンに紐づいた子トークンではない必要があります。
  • 転送先のスマートコントラクトがERC7401をサポートしていることを確認します。
  • トークンの継承ループをチェックし、ループが検出された場合はトランザクションを失敗させます。
  • TransferイベントとNestTransferイベントを発行します。

引数

  • from
    • 現在の所有者のアカウントのアドレス。
  • to
    • トークンを受け取るトークンコレクションのスマートコントラクトのアドレス。
  • tokenId
    • 転送されるトークンの一意の識別子(トークンID)。
  • destinationId
    • 受け取るトークンの識別子。
  • data
    • addChild 呼び出し時に送信される形式の指定されていない追加データ。

_sendToNFT

javascript
function _sendToNFT(
    address from,
    address to,
    uint256 parentId,
    uint256 destinationId,
    uint256 tokenId,
    bytes memory data
) private {
    IERC7401 destContract = IERC7401(to);
    destContract.addChild(destinationId, tokenId, data);

    emit Transfer(from, to, tokenId);
    emit NestTransfer(from, to, parentId, destinationId, tokenId);

    _afterTokenTransfer(from, to, tokenId);
    _afterNestedTokenTransfer(
        from,
        to,
        parentId,
        destinationId,
        tokenId,
        data
    );
}

概要 トークンを別のトークンに送信する関数。

詳細

  • トークンが外部所有アカウントによって所有されている場合、parentIdは0である必要があります。
  • TransferイベントとNestTransferイベントを発行します。

引数

  • from
    • 送信元のアドレス。
  • to
    • トークンを受け取るトークンのコレクションスマートコントラクトのアドレス。
  • parentId
    • 送信中のトークンの現在の親トークンの識別子。
  • destinationId
    • トークンを受け取るトークンの識別子。
  • tokenId
    • 送信されるトークンの一意の識別子(トークンID)。
  • data
    • addChild呼び出し時に送信される形式の指定されていない追加データ。

_checkForInheritanceLoop

javascript
function _checkForInheritanceLoop(
    uint256 currentId,
    address targetContract,
    uint256 targetId
) private view {
    for (uint256 i; i < _MAX_LEVELS_TO_CHECK_FOR_INHERITANCE_LOOP; ) {
        (
            address nextOwner,
            uint256 nextOwnerTokenId,
            bool isNft
        ) = IERC7401(targetContract).directOwnerOf(targetId);
        // If there's a final address, we're good. There's no loop.
        if (!isNft) {
            return;
        }
        // Ff the current nft is an ancestor at some point, there is an inheritance loop
        if (nextOwner == address(this) && nextOwnerTokenId == currentId) {
            revert NestableTransferToDescendant();
        }
        // We reuse the parameters to save some contract size
        targetContract = nextOwner;
        targetId = nextOwnerTokenId;
        unchecked {
            ++i;
        }
    }
    revert NestableTooDeep();
}

概要 指定されたトークンを別のトークンにネストすることで継承ループが発生するかどうかをチェックする関数。

詳細

  • ループが発生した場合、トークンは管理できなくなる可能性があるため、ループが検出された場合はトランザクションを失敗させます。
  • 継承ループのチェックは、ガス消費が多くなりすぎないように制限されています。

引数

  • currentId
    • ネストされるトークンの識別子。
  • targetContract
    • トークンをネストするトークンのコレクションスマートコントラクトのアドレス。
  • targetId
    • トークンをネストするトークンの識別子。

戻り値

  • なし

_safeMint

javascript
function _safeMint(
    address to,
    uint256 tokenId,
    bytes memory data
) internal virtual {
    _mint(to, tokenId, data);
    if (!_checkOnERC721Received(address(0), to, tokenId, data))
        revert ERC721TransferToNonReceiverImplementer();
}

概要 指定されたアドレスにトークンを安全に生成(ミント)し、コントラクトに追加データを渡す関数。

詳細

  • _mint関数を呼び出してトークンを生成し、その後、コントラクトにERC721プロトコルに従ったトランザクションを受け入れるかどうかを確認します。

引数

  • to
    • トークンを生成するアドレス。
  • tokenId
    • 生成するトークンのID。
  • data
    • トークンと一緒に送信する追加データ。

_mint

javascript
function _mint(
    address to,
    uint256 tokenId,
    bytes memory data
) internal virtual {
    _innerMint(to, tokenId, 0, data);

    emit Transfer(address(0), to, tokenId);
    emit NestTransfer(address(0), to, 0, 0, tokenId);

    _afterTokenTransfer(address(0), to, tokenId);
    _afterNestedTokenTransfer(address(0), to, 0, 0, tokenId, data);
}

概要 指定されたアドレスにトークンを生成(ミント)する関数。 この関数の使用は非推奨であり、可能な場合は_safeMintを使用することが推奨されます。

詳細

  • tokenIdがまだ存在していないことを確認します。
  • toがゼロアドレスでないことを確認します。
  • トークンの生成後、TransferイベントとNestTransferイベントを発行します。

引数

  • to
    • トークンを生成するアドレス。
  • tokenId
    • 生成するトークンのID。
  • data
    • トークンと一緒に送信する追加データ。

_nestMint

javascript
function _nestMint(
    address to,
    uint256 tokenId,
    uint256 destinationId,
    bytes memory data
) internal virtual {
    // It seems redundant, but otherwise it would revert with no error
    if (!to.isContract()) revert IsNotContract();
    if (!IERC165(to).supportsInterface(type(IERC7401).interfaceId))
        revert MintToNonNestableImplementer();

    _innerMint(to, tokenId, destinationId, data);
    _sendToNFT(address(0), to, 0, destinationId, tokenId, data);
}

概要 指定された親トークンに子トークンを生成(ミント)する関数。

詳細

  • toがスマートコントラクトであることを確認します。
  • toがERC7401をサポートしていることを確認します。
  • _innerMint関数を呼び出してトークンを生成し、その後、子トークンを親トークンに送信します。

引数

  • to
    • 子トークンを生成する親トークンのコレクションスマートコントラクトのアドレス。
  • tokenId
    • 生成する子トークンのID。
  • destinationId
    • 子トークンが配置される親トークンのID。
  • data
    • 子トークン生成時に送信する追加データ。

_innerMint

javascript
function _innerMint(
    address to,
    uint256 tokenId,
    uint256 destinationId,
    bytes memory data
) private {
    if (to == address(0)) revert ERC721MintToTheZeroAddress();
    if (_exists(tokenId)) revert ERC721TokenAlreadyMinted();
    if (tokenId == uint256(0)) revert IdZeroForbidden();

    _beforeTokenTransfer(address(0), to, tokenId);
    _beforeNestedTokenTransfer(
        address(0),
        to,
        0,
        destinationId,
        tokenId,
        data
    );

    _balances[to] += 1;
    _directOwners[tokenId] = DirectOwner({
        ownerAddress: to,
        tokenId: destinationId
    });
}

概要 指定されたアドレスにトークンを生成(ミント)する関数。

詳細

  • toがゼロアドレスでないことを確認します。
  • tokenIdがまだ存在していないことを確認します。
  • tokenIdがゼロでないことを確認します。
  • トークンの生成後、TransferイベントとNestTransferイベントを発行します。

引数

  • to
    • トークンを生成するアドレス。
  • tokenId
    • 生成するトークンのID。
  • destinationId
    • 生成されるトークンの親トークンのID。
  • data
    • トークン生成時に送信する追加データ。

ownerOf

javascript
function ownerOf(
    uint256 tokenId
) public view virtual override(IERC7401, IERC721) returns (address) {
    (address owner, uint256 ownerTokenId, bool isNft) = directOwnerOf(
        tokenId
    );
    if (isNft) {
        owner = IERC7401(owner).ownerOf(ownerTokenId);
    }
    return owner;
}

概要 指定されたトークンのルートオーナーを取得する関数。

詳細

  • directOwnerOf関数を呼び出してトークンの直接の所有者を取得し、その所有者がNFTである場合はそのNFTのルートオーナーを再帰的に取得します。

引数

  • tokenId
    • ルートオーナーを取得するトークンのID。

戻り値

  • トークンのルートオーナーのアドレス。

directOwnerOf

javascript
function directOwnerOf(
    uint256 tokenId
) public view virtual returns (address, uint256, bool) {
    DirectOwner memory owner = _directOwners[tokenId];
    if (owner.ownerAddress == address(0)) revert ERC721InvalidTokenId();

    return (owner.ownerAddress, owner.tokenId, owner.tokenId != 0);
}

概要 指定されたトークンの直接の所有者を取得する関数。

詳細

  • トークンの直接の所有者情報を_directOwnersマップから取得し、その所有者が外部所有アカウントかどうかを判定します。

引数

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

戻り値

  • 直接の所有者のアドレス、トークンID、および直接の所有者がトークンであるかどうかを示すブール値。

burn

javascript
function burn(
    uint256 tokenId,
    uint256 maxChildrenBurns
) public virtual onlyApprovedOrDirectOwner(tokenId) returns (uint256) {
    return _burn(tokenId, maxChildrenBurns);
}

**概要

** 指定されたトークンを燃やす(削除する)関数。

詳細

  • onlyApprovedOrDirectOwner修飾子を使用して、トークンの所有者または承認済みのアカウントのみがこの関数を呼び出せるようにします。
  • _burn関数を呼び出してトークンを燃やし、子トークンも再帰的に燃やします。
  • トークンが燃やされると、承認情報もクリアされます。

引数

  • tokenId
    • 燃やすトークンのID。

戻り値

  • 燃やされた子トークンの数。

_burn

javascript
function _burn(
    uint256 tokenId,
    uint256 maxChildrenBurns
) internal virtual returns (uint256) {
    (address immediateOwner, uint256 parentId, ) = directOwnerOf(tokenId);
    address rootOwner = ownerOf(tokenId);

    _beforeTokenTransfer(immediateOwner, address(0), tokenId);
    _beforeNestedTokenTransfer(
        immediateOwner,
        address(0),
        parentId,
        0,
        tokenId,
        ""
    );

    _balances[immediateOwner] -= 1;
    _approve(address(0), tokenId);
    _cleanApprovals(tokenId);

    Child[] memory children = childrenOf(tokenId);

    delete _activeChildren[tokenId];
    delete _pendingChildren[tokenId];
    delete _tokenApprovals[tokenId][rootOwner];

    uint256 pendingRecursiveBurns;
    uint256 totalChildBurns;

    uint256 length = children.length; //gas savings
    for (uint256 i; i < length; ) {
        if (totalChildBurns >= maxChildrenBurns)
            revert MaxRecursiveBurnsReached(
                children[i].contractAddress,
                children[i].tokenId
            );
        delete _childIsInActive[children[i].contractAddress][
            children[i].tokenId
        ];
        unchecked {
            // At this point we know pendingRecursiveBurns must be at least 1
            pendingRecursiveBurns = maxChildrenBurns - totalChildBurns;
        }
        // We substract one to the next level to count for the token being burned, then add it again on returns
        // This is to allow the behavior of 0 recursive burns meaning only the current token is deleted.
        totalChildBurns +=
            IERC7401(children[i].contractAddress).burn(
                children[i].tokenId,
                pendingRecursiveBurns - 1
            ) +
            1;
        unchecked {
            ++i;
        }
    }
    // Can't remove before burning child since child will call back to get root owner
    delete _directOwners[tokenId];

    emit Transfer(immediateOwner, address(0), tokenId);
    emit NestTransfer(immediateOwner, address(0), parentId, 0, tokenId);

    _afterTokenTransfer(immediateOwner, address(0), tokenId);
    _afterNestedTokenTransfer(
        immediateOwner,
        address(0),
        parentId,
        0,
        tokenId,
        ""
    );

    return totalChildBurns;
}

概要 指定されたトークンを燃やす(削除する)関数。

詳細

  • directOwnerOf関数を使用して、トークンの直接の所有者情報を取得し、ルートオーナーを特定します。
  • onlyApprovedOrDirectOwner修飾子を使用して、トークンの所有者または承認済みのアカウントのみがこの関数を呼び出せるようにします。
  • 子トークンも再帰的に燃やされます。
  • トークンが燃やされると、トークンとその子トークンに関連するデータがクリアされます。
  • TransferイベントとNestTransferイベントが発行されます。

引数

  • tokenId
    • 燃やすトークンのID。
  • maxChildrenBurns
    • 再帰的に燃やす子トークンの最大数。

戻り値

  • 燃やされた子トークンの数。

approve

javascript
function approve(address to, uint256 tokenId) public virtual {
    address owner = ownerOf(tokenId);
    if (to == owner) revert ERC721ApprovalToCurrentOwner();

    if (_msgSender() != owner && !isApprovedForAll(owner, _msgSender()))
        revert ERC721ApproveCallerIsNotOwnerNorApprovedForAll();

    _approve(to, tokenId);
}

概要 指定されたトークンの管理権限を別のアドレスに付与する関数。

詳細

  • トークンの現在の所有者を取得し、toが現在の所有者である場合はエラーを発生させます。
  • 呼び出し元がトークンの所有者でもなく、または所有者からの承認が得られていない場合はエラーを発生させます。
  • _approve関数を呼び出して承認情報を更新します。

引数

  • to
    • 承認を付与するアドレス。
  • tokenId
    • 承認対象のトークンのID。

getApproved

javascript
function getApproved(uint256 tokenId) public view virtual returns (address) {
    _requireMinted(tokenId);

    return _tokenApprovals[tokenId][ownerOf(tokenId)];
}

概要 指定されたトークンに対する承認されたアドレスを取得する関数。

詳細

  • トークンが存在することを確認します。
  • _tokenApprovalsマップからトークンの所有者に関連付けられた承認アドレスを取得します。

引数

  • tokenId
    • 承認情報を取得するトークンのID。

戻り値

  • トークンに対する承認されたアドレス。

setApprovalForAll

javascript
function setApprovalForAll(address operator, bool approved) public virtual {
    if (_msgSender() == operator) revert ERC721ApproveToCaller();
    _operatorApprovals[_msgSender()][operator] = approved;
    emit ApprovalForAll(_msgSender(), operator, approved);
}

概要 指定されたオペレーターに対するすべてのトークンの管理権限を設定または解除する関数。

詳細

  • 呼び出し元が自身に対して承認を設定しようとした場合はエラーを発生させます。
  • _operatorApprovalsマップを更新してオペレーターに対する承認を設定または解除します。
  • ApprovalForAllイベントを発行して変更を通知します。

引数

  • operator
    • 承認状態を設定または解除するオペレーターのアドレス。
  • approved
    • 承認を設定する場合はtrue、解除する場合はfalse。

isApprovedForAll

javascript
function isApprovedForAll(address owner, address operator) public view virtual returns (bool) {
    return _operatorApprovals[owner][operator];
}

概要 指定された所有者とオペレーターの間で管理権限が設定されているか確認する関数。

詳細

  • _operatorApprovalsマップから所有者とオペレーターの関係を確認し、管理権限が設定されているかどうかを返します。

引数

  • owner
    • 所有者のアドレス。
  • operator
    • オペレーターのアドレス。

戻り値

  • 管理権限が設定されている場合はtrue、それ以外の場合はfalse。

_approve

javascript
function _approve(address to, uint256 tokenId) internal virtual {
    address owner = ownerOf(tokenId);
    _tokenApprovals[tokenId][owner] = to;
    emit Approval(owner, to, tokenId);
}

概要 指定されたトークンに対する承認情報を更新する関数。

詳細

  • トークンの所有者を取得します。
  • _tokenApprovalsマップを更新してトークンに対する新しい承認アドレスを設定します。
  • Approvalイベントを発行して変更を通知します。

引数

  • to
    • 承認を設定する新しいアドレス。
  • tokenId
    • 承認を更新するトークンのID。

_updateOwnerAndClearApprovals

javascript
function _updateOwnerAndClearApprovals(uint256 tokenId, uint256 destinationId, address to) internal {
    _directOwners[tokenId] = DirectOwner({
        ownerAddress: to,
        tokenId: destinationId
    });

    // 前の所有者からの承認情報をクリアします
    _approve(address(0), tokenId);
    _cleanApprovals(tokenId);
}

概要 トークンの新しい所有者を更新し、以前の所有者に関連する承認情報をクリアする関数。

詳細

  • _directOwnersマップを更新してトークンの新しい所有者と親トークンのIDを設定します。
  • 以前の所有者からの承認情報をクリアします。

引数

  • tokenId
    • 更新されるトークンのID。
  • destinationId
    • 受信トークンのID(新しい所有者がトークンでない場合は0に設定)。
  • to
    • 新しい所有者のアドレス。

_cleanApprovals

javascript
function _cleanApprovals(uint256 tokenId) internal virtual {}

概要 指定されたトークンに関連する承認情報をクリアする関数。

引数

  • tokenId
    • 承認情報をクリアするトークンのID。

_isApprovedOrOwner

javascript
function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {
    address owner = ownerOf(tokenId);
    return (spender == owner ||
        isApprovedForAll(owner, spender) ||
        getApproved(tokenId) == spender);
}

概要 指定されたアドレスがトークンを管理できるか確認する関数。

詳細

  • 所有者、または所有者からの承認、またはトークンに対する承認が設定されている場合、trueを返します。

引数

  • spender
    • 承認または所有権を確認するアドレス。
  • tokenId
    • 承認または所有権を確認するトークンのID。

戻り値

  • アドレスがトークンを管理できる場合はtrue、それ以外の場合はfalse。

_isApprovedOrDirectOwner

javascript
function _isApprovedOrDirectOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {
    (address owner, uint256 parentId, ) = directOwnerOf(tokenId);
    if (parentId != 0) {
        return (spender == owner);
    }
    return (spender == owner ||
        isApprovedForAll(owner, spender) ||
        getApproved(tokenId) == spender);
}

概要 指定されたアドレスがトークンまたはその直接の所有者を管理できるか確認する関数。

詳細

  • 親トークンがNFTである場合、トークンを操作できるのは親トークンだけです。
  • それ以外の場合、所有者、または所有者からの承認、またはトークンに対する承認が設定されている場合、true を返します。

引数

  • spender
    • 承認または所有権を確認するアドレス。
  • tokenId
    • 承認または所有権を確認するトークンのID。

戻り値

  • アドレスがトークンまたはその直接の所有者を管理できる場合はtrue、それ以外の場合はfalse。

_requireMinted

javascript
function _requireMinted(uint256 tokenId) internal view virtual {
    if (!_exists(tokenId)) revert ERC721InvalidTokenId();
}

概要 指定されたトークンが発行済みか確認する関数。

詳細

  • _exists関数を使用してトークンが存在するかどうかを確認します。
  • トークンが存在しない場合、ERC721InvalidTokenIdエラーを発生させます。

引数

  • tokenId
    • 存在を確認するトークンのID。

_exists

javascript
function _exists(uint256 tokenId) internal view virtual returns (bool) {
    return _directOwners[tokenId].ownerAddress != address(0);
}

概要 指定されたトークンが存在するか確認する関数。

詳細

  • _directOwnersマップからトークンの所有者情報を取得し、所有者のアドレスが0x0でない場合、トークンが存在すると判断します。
  • トークンが存在する場合、trueを返します。

引数

  • tokenId
    • 存在を確認するトークンのID。

戻り値

  • トークンが存在する場合はtrue、それ以外の場合はfalse。

_checkOnERC721Received

javascript
function _checkOnERC721Received(
    address from,
    address to,
    uint256 tokenId,
    bytes memory data
) private returns (bool) {
    if (to.isContract()) {
        try
            IERC721Receiver(to).onERC721Received(
                _msgSender(),
                from,
                tokenId,
                data
            )
        returns (bytes4 retval) {
            return retval == IERC721Receiver.onERC721Received.selector;
        } catch (bytes memory reason) {
            if (reason.length == uint256(0)) {
                revert ERC721TransferToNonReceiverImplementer();
            } else {
                /// @solidity memory-safe-assembly
                assembly {
                    revert(add(32, reason), mload(reason))
                }
            }
        }
    } else {
        return true;
    }
}

概要 指定されたアドレスがコントラクトである場合に、IERC721Receiverインターフェースの onERC721Received関数を呼び出す関数。

詳細

  • toがコントラクトである場合、IERC721ReceiverインターフェースのonERC721Received関数を呼び出し、戻り値が期待される関数シグネチャであることを確認します。
  • 呼び出しに成功した場合、trueを返します。
  • 呼び出しに失敗した場合、エラーメッセージが空であるかどうかを確認し、空でない場合はERC721TransferToNonReceiverImplementerエラーを発生させます。
  • 空でない場合はアセンブリを使用してエラーメッセージを再度発生させます。
  • toがコントラクトでない場合、trueを返します。

引数

  • from
    • 以前のトークンの所有者アドレス。
  • to
    • トークンを受け取るアドレス。
  • tokenId
    • 転送されるトークンのID。
  • data
    • 呼び出しと一緒に送信されるオプションのデータ。

戻り値

  • 呼び出しが正常に返された場合はtrue、それ以外の場合はfalse。

addChild

javascript
function addChild(
    uint256 parentId,
    uint256 childId,
    bytes memory data
) public virtual {
    _requireMinted(parentId);

    address childAddress = _msgSender();
    if (!childAddress.isContract()) revert IsNotContract();

    Child memory child = Child({
        contractAddress: childAddress,
        tokenId: childId
    });

    _beforeAddChild(parentId, childAddress, childId, data);

    uint256 length = pendingChildrenOf(parentId).length;

    if (length < 128) {
        _pendingChildren[parentId].push(child);
    } else {
        revert MaxPendingChildrenReached();
    }

    // Previous length matches the index for the new child
    emit ChildProposed(parentId, length, childAddress, childId);

    _afterAddChild(parentId, childAddress, childId, data);
}

概要 指定された親トークンに子トークンを追加する関数。

詳細

  • 子トークンをNFTに追加するには、nestTransfer、nestMint、transferChildを使用する必要があります。
  • 親トークンが指定された子トークンの所有者であることを確認します。
  • 親トークンの保留中の子トークン配列が128未満であることを確認します。
  • 子トークン情報を作成し、親トークンの保留中の子トークン配列に追加します。
  • 保留中の子トークンが追加されたことを通知します。

引数

  • parentId
    • 新しい子トークンを受け取る親トークンのID。
  • childId
    • 追加する新しい子トークンのID。
  • data
    • 指定されたフォーマットの追加データ。

acceptChild

javascript
function acceptChild(
    uint256 parentId,
    uint256 childIndex,
    address childAddress,
    uint256 childId
) public virtual onlyApprovedOrOwner(parentId) {
    _acceptChild(parentId, childIndex, childAddress, childId);
}

概要 指定された親トークンに対して保留中の子トークンを受け入れる関数。

詳細

  • 親トークンが指定された子トークンの所有者であることを確認します。
  • 予想される子トークン情報と実際の子トークン情報が一致することを確認します。
  • 既にアクティブな子トークンでないことを確認します。
  • 保留中の子トークンを削除し、アクティブな子トークンに追加します。
  • 子トークンが正常に受け入れられたことを通知します。

引数

  • parentId
    • 子トークンが受け入れられる親トークンのID。
  • childIndex
    • 親トークンの保留中の子トークン配列内の子トークンのインデックス。
  • childAddress
    • 子トークンのコレクションスマートコントラクトのアドレス。
  • childId
    • 子トークンのコレクションスマートコントラクト内の子トークンのID。

_acceptChild

javascript
function _acceptChild(
    uint256 parentId,
    uint256 childIndex,
    address childAddress,
    uint256 childId
) internal virtual {
    Child memory child = pendingChildOf(parentId, childIndex);
    _checkExpectedChild(child, childAddress, childId);
    if (_childIsInActive[childAddress][childId] != 0)
        revert ChildAlreadyExists();

    _beforeAcceptChild(parentId, childIndex, childAddress, childId);

    // Remove from pending:
    _removeChildByIndex(_pendingChildren[parentId], childIndex);

    // Add to active:
    _activeChildren[parentId].push(child);
    _childIsInActive[childAddress][childId] = 1; // We use 1 as true

    emit ChildAccepted(parentId, childIndex, childAddress, childId);

    _afterAcceptChild(parentId, childIndex, childAddress, childId);
}

概要 指定された親トークンに対して保留中の子トークンを受け入れる関数。 保留中の子トークンは親トークンのアクティブな子トークンに移動します。

詳細

  • 予想される子トークン情報と実際の子トークン情報が一致することを確認します。
  • 既にアクティブな子トークンでないことを確認します。
  • 保留中の子トークンを削除し、アクティブな子トークンに追加します。
  • 子トークンが正常に受け入れられたことを通知します。

引数

  • parentId
    • 子トークンが受け入れられる親トークンのID。
  • childIndex
    • 親トークンの保留中の子トークン配列内の子トークンのインデックス。
  • childAddress
    • 子トークンのコレクションスマートコントラクトのアドレス。
  • childId
    • 子トークンのコレクションスマートコントラクト内の子トークンのID。

rejectAllChildren

javascript
function rejectAllChildren(
    uint256 tokenId,
    uint256 maxRejections
) public virtual onlyApprovedOrOwner(tokenId) {
    _rejectAllChildren(tokenId, maxRejections);
}

概要 指定された親トークンから保留中の子トークンをすべて拒否する関数。

詳細

  • 子トークンは保留中の子トークンのマッピングから削除されます。
  • 保留中の子トークンの数がmaxRejectionsを超える場合、エラーを発生させます。
  • 保留中の子トークンをすべて削除し、イベントを通知します。
  • 子トークンの所有権のデータは更新されません。必要であれば、前の親のrootOwnerによって所有権を取り戻すことができます。

引数

  • tokenId
    • すべての保留中のトークンを拒否する親トークンのID。
  • maxRejections
    • 拒否する予定の子トークンの最大数。

_rejectAllChildren

javascript
function _rejectAllChildren(
    uint256 tokenId,
    uint256 maxRejections
) internal virtual {
    if (_pendingChildren[tokenId].length > maxRejections)
        revert UnexpectedNumberOfChildren();

    _beforeRejectAllChildren(tokenId);
    delete _pendingChildren[tokenId];
    emit AllChildrenRejected(tokenId);
    _afterRejectAllChildren(tokenId);
}

概要 指定された親トークンから保留中の子トークンをすべて拒否する関数。

詳細

  • 子トークンは保留中の子トークンのマッピングから削除されます。
  • 保留中の子トークンの数がmaxRejectionsを超える場合、エラーを発生させます。
  • 保留中の子トークンをすべて削除し、イベントを通知します。

引数

  • tokenId
    • すべての保留中のトークンを拒否する親トークンのID。
  • maxRejections
    • 拒否する予定の子トークンの最大数。

transferChild

javascript
function transferChild(
    uint256 tokenId,
    address to,
    uint256 destinationId,
    uint256 childIndex,
    address childAddress,
    uint256 childId,
    bool isPending,
    bytes memory data
) public virtual onlyApprovedOrOwner(tokenId)

概要 指定された親トークンから子トークンを転送する関数。

詳細

  • 子トークンは別のアドレスに転送され、必要に応じて新しい親トークンに関連付けられます。
  • 親トークンが指定された子トークンの所有者であることを確認します。
  • 子トークンが保留中の子トークンであるか、アクティブな子トークンであるかに応じて転送を処理します。
  • 転送が成功した場合、関連するイベントを通知します。

引数

  • tokenId
    • 子トークンを転送する親トークンのID。
  • to
    • 子トークンを転送する先のアドレス。
  • destinationId
    • 子トークンを受け取るトークンのID(トークンでない場合は0に設定)。
  • childIndex
    • 転送する子トークンの親トークン内のインデックス。
  • childAddress
    • 子トークンのコレクションスマートコントラクトのアドレス。
  • childId
    • 子トークンのコレクションスマートコントラクト内の子トークンのID。
  • isPending
    • 子トークンが保留中の子トークンであるかどうかを示すブール値。
  • data
    • 指定されたフォーマットの追加データ。

transferChild

javascript
function transferChild(
    uint256 tokenId,
    address to,
    uint256 destinationId,
    uint256 childIndex,
    address childAddress,
    uint256 childId,
    bool isPending,
    bytes memory data
) public virtual onlyApprovedOrOwner(tokenId) {
    _transferChild(
        tokenId,
        to,
        destinationId,
        childIndex,
        childAddress,
        childId,
        isPending,
        data
    );
}

概要 指定された親トークンから子トークンを転送する関数。

詳細

  • 子トークンは別のアドレスに転送され、必要に応じて新しい親トークンに関連付けられます。
  • 親トークンが指定された子トークンの所有者であることを確認します。
  • 子トークンが保留中の子トークンであるか、アクティブな子トークンであるかに応じて転送を処理します。
  • 転送が成功した場合、関連するイベントを通知します。

引数

  • tokenId: 子トークンを転送する親トークンのID。
  • to
    • 子トークンを転送する先のアドレス。
  • destinationId
    • 子トークンを受け取るトークンのID(トークンでない場合は0に設定)。
  • childIndex
    • 転送する子トークンの親トークン内のインデックス。
  • childAddress
    • 子トークンのコレクションスマートコントラクトのアドレス。
  • childId
    • 子トークンのコレクションスマートコントラクト内の子トークンのID。
  • isPending
    • 子トークンが保留中の子トークンであるかどうかを示すブール値。
  • data
    • 指定されたフォーマットの追加データ。

戻り値

  • なし

_transferChild

javascript
function _transferChild(
    uint256 tokenId,
    address to,
    uint256 destinationId,
    uint256 childIndex,
    address childAddress,
    uint256 childId,
    bool isPending,
    bytes memory data
) internal virtual {
    Child memory child;
    if (isPending) {
        child = pendingChildOf(tokenId, childIndex);
    } else {
        child = childOf(tokenId, childIndex);
    }
    _checkExpectedChild(child, childAddress, childId);

    _beforeTransferChild(
        tokenId,
        childIndex,
        childAddress,
        childId,
        isPending,
        data
    );

    if (isPending) {
        _removeChildByIndex(_pendingChildren[tokenId], childIndex);
    } else {
        delete _childIsInActive[childAddress][childId];
        _removeChildByIndex(_activeChildren[tokenId], childIndex);
    }

    if (to != address(0)) {
        if (destinationId == uint256(0)) {
            IERC721(childAddress).safeTransferFrom(
                address(this),
                to,
                childId,
                data
            );
       

 } else {
            // Destination is an NFT
            IERC7401(child.contractAddress).nestTransferFrom(
                address(this),
                to,
                child.tokenId,
                destinationId,
                data
            );
        }
    }

    emit ChildTransferred(
        tokenId,
        childIndex,
        childAddress,
        childId,
        isPending,
        to == address(0)
    );
    _afterTransferChild(
        tokenId,
        childIndex,
        childAddress,
        childId,
        isPending,
        data
    );
}

概要 指定された親トークンから子トークンを転送する関数。

詳細

  • 子トークンは別のアドレスに転送され、必要に応じて新しい親トークンに関連付けられます。
  • 子トークンの期待されるアドレスとIDが一致することを確認します。
  • 転送前の処理を _beforeTransferChildに委託します。
  • isPendingに応じて保留中の子トークンまたはアクティブな子トークンを削除します。
  • 転送がtoアドレスが0x0でない場合、子トークンを転送します。
  • 転送が成功した場合、関連するイベントを通知します。

引数

  • tokenId
    • 子トークンを転送する親トークンのID。
  • to
    • 子トークンを転送する先のアドレス。
  • destinationId
    • 子トークンを受け取るトークンのID(トークンでない場合は0に設定)。
  • childIndex
    • 転送する子トークンの親トークン内のインデックス。
  • childAddress
    • 子トークンのコレクションスマートコントラクトのアドレス。
  • childId
    • 子トークンのコレクションスマートコントラクト内の子トークンのID。
  • isPending
    • 子トークンが保留中の子トークンであるかどうかを示すブール値。
  • data
    • 指定されたフォーマットの追加データ

_checkExpectedChild

javascript
function _checkExpectedChild(
    Child memory child,
    address expectedAddress,
    uint256 expectedId
) private pure {
    if (
        expectedAddress != child.contractAddress ||
        expectedId != child.tokenId
    ) revert UnexpectedChildId();
}

概要 アクセスされている子トークンが意図した子トークンであることを確認する関数。

詳細

  • Child構造体の情報と期待されるアドレスおよびIDが一致しない場合、UnexpectedChildIdエラーを発生させます。
  • Child構造体は、tokenIdとcontractAddressの2つの値から構成されます。

引数

  • child
    • アクセスされている子トークンのChild構造体。
  • expectedAddress
    • 子トークンの期待されるアドレス。
  • expectedId
    • 子トークンの期待されるID。

childrenOf

javascript
function childrenOf(
    uint256 parentId
) public view virtual returns (Child[] memory) {
    Child[] memory children = _activeChildren[parentId];
    return children;
}

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

詳細

  • Child構造体は、tokenIdとcontractAddressの2つの値から構成されます。

引数

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

戻り値

  • Child構造体の配列
    • 親トークンのアクティブな子トークンを含むChild構造体の配列。

pendingChildrenOf

javascript
function pendingChildrenOf(
    uint256 parentId
) public view virtual returns (Child[] memory) {
    Child[] memory pendingChildren = _pendingChildren[parentId];
    return pendingChildren;
}

概要 指定された親トークンの保留中の子トークンを取得する関数。

詳細

  • 親トークンの保留中の子トークンを含むChild構造体の配列を返します。
  • Child構造体は、tokenIdとcontractAddressの2つの値から構成されます。

引数

  • parentId
    • 保留中の子トークンを取得する親トークンのID。

戻り値

  • Child構造体の配列
    • 親トークンの保留中の子トークンを含むChild構造体の配列。

childOf

javascript
function childOf(
    uint256 parentId,
    uint256 index
) public view virtual returns (Child memory) {
    if (childrenOf(parentId).length <= index)
        revert ChildIndexOutOfRange();
    Child memory child = _activeChildren[parentId][index];
    return child;
}

概要 指定された親トークンの特定のアクティブな子トークンを取得する関数。

詳細

  • 親トークンのアクティブな子トークンを含むChild構造体の配列から、指定されたインデックスのChild構造体を取得します。
  • Child構造体はtokenIdとcontractAddressの2つの値から構成されます。
  • indexがアクティブな子トークンの配列の範囲外の場合、ChildIndexOutOfRangeエラーを発生させます。

引数

  • parentId
    • 取得対象のアクティブな子トークンを持つ親トークンのID。
  • index
    • 親トークンのアクティブな子トークン配列内の子トークンのインデックス。

戻り値

  • Child構造体
    • 指定された子トークンに関する情報を含むChild構造体。

pendingChildOf

javascript
function pendingChildOf(
    uint256 parentId,
    uint256 index
) public view virtual returns (Child memory) {
    if (pendingChildrenOf(parentId).length <= index)
        revert PendingChildIndexOutOfRange();
    Child memory child = _pendingChildren[parentId][index];
    return child;
}

概要 指定された親トークンの特定の保留中の子トークンを取得する関数。

詳細

  • 親トークンの保留中の子トークンを含むChild構造体の配列から、指定されたインデックスのChild構造体を取得します。
  • Child構造体はtokenIdとcontractAddressの2つの値から構成されます。
  • indexが保留中の子トークンの配列の範囲外の場合、PendingChildIndexOutOfRangeエラーを発生させます。

引数

  • parentId
    • 取得対象の保留中の子トークンを持つ親トークンのID。
  • index
    • 親トークンの保留中の子トークン配列内の子トークンのインデックス。

戻り値

  • Child構造体
    • 指定された子トークンに関する情報を含むChild構造体。

_beforeTokenTransfer

javascript
/**
 * @notice トークンの転送前に呼び出されるフックです。これには鋳造(minting)および焼却(burning)も含まれます。
 * @dev 呼び出しの条件:
 *
 *  - `from` と `to` が両方ともゼロでない場合、`from` の `tokenId` は `to` に転送されます。
 *  - `from` がゼロの場合、`tokenId` は `to` に鋳造されます。
 *  - `to` がゼロの場合、`from` の `tokenId` は焼却されます。
 *  - `from` と `to` は同時にゼロになりません。
 *
 *  フックについて詳しくは、xref:ROOT:extending-contracts.adoc#using-hooks[フックの使用]を参照してください。
 * @param from トークンが転送されるアドレス
 * @param to トークンが転送されるアドレス
 * @param tokenId 転送されるトークンのID
 */
function _beforeTokenTransfer(
    address from,
    address to,
    uint256 tokenId
) internal virtual {}

概要 この関数は、トークンの転送(鋳造および焼却も含む)の前に呼び出されるフックです。トークンの転送に関する特定の条件を確認および処理するために使用できます。

詳細

  • from から to へのトークンの転送が行われる前に呼び出されます。
  • 特定の呼び出し条件に応じて、トークンの鋳造、焼却、転送の処理をカスタマイズできます。
  • from と to が両方ともゼロになることはありません。

引数

  • from: トークンが転送されるアドレス
  • to: トークンが転送されるアドレス
  • tokenId: 転送されるトークンのID

_afterTokenTransfer

javascript
function _afterTokenTransfer(
    address from,
    address to,
    uint256 tokenId
) internal virtual {}

概要 トークンの転送(鋳造および焼却も含む)の後に呼び出されるフック。 トークンの転送後の処理をカスタマイズするために使用できます。

詳細

  • fromからtoへのトークンの転送が行われた後に呼び出されます。
  • 特定の呼び出し条件に応じて、トークンの転送後の処理をカスタマイズできます。
  • fromとtoが両方ともゼロになることはありません。

引数

  • from
    • トークンが転送されたアドレス。
  • to
    • トークンが転送されたアドレス。
  • tokenId
    • 転送されたトークンのID。

_beforeNestedTokenTransfer

javascript
function _beforeNestedTokenTransfer(
    address from,
    address to,
    uint256 fromTokenId,
    uint256 toTokenId,
    uint256 tokenId,
    bytes memory data
) internal virtual {}

概要 ネストされたトークンの転送(子トークンから親トークンへの転送)の前に呼び出されるフック。 ネストされたトークンの転送前の特定の処理をカスタマイズするために使用できます。

詳細

  • ネストされたトークン(子トークンから親トークンへの転送)が行われる前に呼び出されます。
  • ネストされたトークンの転送前の処理をカスタマイズするために使用できます。
  • fromとtoのアドレス、転送元および転送先のトークンのID、転送されるトークンのID、および追加データが提供されます。

引数

  • from
    • トークンが転送されるアドレス。
  • to
    • トークンが転送されるアドレス。
  • fromTokenId
    • 転送元のトークンのID。
  • toTokenId
    • 転送先のトークンのID。
  • tokenId
    • 転送されるトークンのID。
  • data
    • 指定されたフォーマットのない追加データ(addChild呼び出しで送信)。

_afterNestedTokenTransfer

javascript
function _afterNestedTokenTransfer(
    address from,
    address to,
    uint256 fromTokenId,
    uint256 toTokenId,
    uint256 tokenId,
    bytes memory data
) internal virtual {}

概要 ネストされたトークンの転送(子トークンから親トークンへの転送)の後に呼び出されるフック。 ネストされたトークンの転送後の特定の処理をカスタマイズするために使用できます。

詳細

  • ネストされたトークン(子トークンから親トークンへの転送)が行われた後に呼び出されます。
  • ネストされたトークンの転送後の処理をカスタマイズするために使用できます。
  • fromとtoのアドレス、転送元および転送先のトークンのID、転送されるトークンのID、および追加データが提供されます。

引数

  • from
    • トークンが転送されたアドレス。
  • to
    • トークンが転送されたアドレス。
  • fromTokenId
    • 転送元のトークンのID。
  • toTokenId
    • 転送先のトークンのID。
  • tokenId
    • 転送されるトークンのID。
  • data
    • 指定されたフォーマットのない追加データ(addChild呼び出しで送信)。

_beforeAddChild

javascript
function _beforeAddChild(
    uint256 tokenId,
    address childAddress,
    uint256 childId,
    bytes memory data
) internal virtual {}

概要 親トークンの保留中の子トークン配列に新しい保留中の子トークンが追加される前に呼び出されるフック。 保留中の子トークンの追加前の特定の処理をカスタマイズするために使用できます。

詳細

  • 親トークンの保留中の子トークン配列に新しい保留中の子トークンが追加される前に呼び出されます。
  • 保留中の子トークンの追加前の処理をカスタマイズするために使用できます。
  • tokenId、childAddress、childId、および追加データが提供されます。

引数

  • tokenId
    • 新しい保留中の子トークンを受け取るトークンのID。
  • childAddress
    • 保留中の子トークンが指定の親トークンの保留中の子トークン配列の指定インデックスに存在すると予想される子トークンのコレクションスマートコントラクトのアドレス。
  • childId
    • 指定の親トークンの保留中の子トークン配列の指定インデックスに存在すると予想される子トークンのID。
  • data
    • 指定されたフォーマットのない追加データ。

_afterAddChild

javascript
function _afterAddChild(
    uint256 tokenId,
    address childAddress,
    uint256 childId,
    bytes memory data
) internal virtual {}

概要 親トークンの保留中の子トークン配列に新しい保留中の子トークンが追加された後に呼び出されるフック。 保留中の子トークンの追加後の特定の処理をカスタマイズするために使用できます。

詳細

  • 親トークンの保留中の子トークン配列に新しい保留中の子トークンが追加された後に呼び出されます。
  • 保留中の子トークンの追加後の処理をカスタマイズするために使用できます。
  • tokenId、childAddress、childId、および追加データが提供されます。

引数

  • tokenId: 新しい保留中の子トークンを受け取るトークンのID。
  • childAddress
    • 保留中の子トークンが指定の親トークンの保留中の子トークン配列の指定インデックスに存在すると予想される子トークンのコレクションスマートコントラクトのアドレス。
  • childId
    • 指定の親トークンの保留中の子トークン配列の指定インデックスに存在すると予想される子トークンのID。
  • data
    • 指定されたフォーマットのない追加データ。

_beforeAcceptChild

javascript
function _beforeAcceptChild(
    uint256 parentId,
    uint256 childIndex,
    address childAddress,
    uint256 childId
) internal virtual {}

概要 親トークンが保留中の子トークンを受け入れる前に呼び出されるフック。 保留中の子トークンを受け入れる前の特定の処理をカスタマイズするために使用できます。

詳細

  • 親トークンが保留中の子トークンを受け入れる前に呼び出されます。
  • 保留中の子トークンを受け入れる前の処理をカスタマイズするために使用できます。
  • parentId、childIndex、childAddress、childId が提供されます。

引数

  • parentId
    • 保留中の子トークンを受け入れるトークンのID。
  • childIndex
    • 指定の親トークンの保留中の子トークン配列で受け入れる子トークンのインデックス。
  • childAddress
    • 保留中の子トークンが指定の親トークンの保留中の子トークン配列の指定インデックスに存在すると予想される子トークンのコレクションスマートコントラクトのアドレス。
  • childId
    • 指定の親トークンの保留中の子トークン配列の指定インデックスに存在すると予想される子トークンのID。

_afterAcceptChild

javascript
function _afterAcceptChild(
    uint256 parentId,
    uint256 childIndex,
    address childAddress,
    uint256 childId
) internal virtual {}

概要 親トークンが保留中の子トークンを受け入れた後に呼び出されるフック。 保留中の子トークンを受け入れた後の特定の処理をカスタマイズするために使用できます。

詳細

  • 親トークンが保留中の子トークンを受け入れた後に呼び出されます。
  • 保留中の子トークンを受け入れた後の処理をカスタマイズするために使用できます。
  • parentId、childIndex、childAddress、childId が提供されます。

引数

  • parentId
    • 保留中の子トークンを受け入れるトークンのID。
  • childIndex
    • 指定の親トークンの保留中の子トークン配列で受け入れる子トークンのインデックス。
  • childAddress
    • 保留中の子トークンが指定の親トークンの保留中の子トークン配列の指定インデックスに存在すると予想される子トークンのコレクションスマートコントラクトのアドレス。
  • childId
    • 指定の親トークンの保留中の子トークン配列の指定インデックスに存在すると予想される子トークンのID。

_beforeTransferChild

javascript
function _beforeTransferChild(
    uint256 tokenId,
    uint256 childIndex,
    address childAddress,
    uint256 childId,
    bool isPending,
    bytes memory data
) internal virtual {}

概要 特定の親トークンの子トークン配列から子トークンを転送する前に呼び出されるフック。 子トークンの転送前の特定の処理をカスタマイズするために使用できます。

詳細

  • 特定の親トークンの子トークン配列から子トークンを転送する前に呼び出されます。
  • 子トークンの転送前の処理をカスタマイズするために使用できます。
  • tokenId、childIndex、childAddress、childId、isPending、および追加データが提供されます。

引数

  • tokenId
    • 子トークンを転送するトークンのID。
  • childIndex
    • 親トークンの子トークン配列から転送される子トークンのインデックス。
  • childAddress
    • 子トークンのコレクションスマートコントラクトのアドレス。指定の親トークンの子トークン配列の指定インデックスに存在することが期待されます。
  • childId
    • 親トークンの子トークン配列の指定インデックスに存在することが期待される子トークンのID。
  • isPending
    • 保留中の子トークンから転送される場合 (true) またはアクティブな子トークンから転送される場合 (false) を示すブール値。
  • data
    • 指定されたフォーマットのない追加データ。
    • addChild呼び出しで送信されます。

_afterTransferChild

javascript
function _afterTransferChild(
    uint256 tokenId,
    uint256 childIndex,
    address childAddress,
    uint256 childId,
    bool isPending,
    bytes memory data
) internal virtual {}

概要 特定の親トークンの子トークン配列から子トークンが転送された後に呼び出されるフック。 子トークンの転送後の特定の処理をカスタマイズするために使用できます。

詳細

  • 特定の親トークンの子トークン配列から子トークンが転送された後に呼び出されます。
  • 子トークンの転送後の処理をカスタマイズするために使用できます。
  • tokenId、childIndex、childAddress、childId、isPending、および追加データが提供されます。

引数

  • tokenId
    • 子トークンが転送されたトークンのID。
  • childIndex
    • 親トークンの子トークン配列から転送された子トークンのインデックス。
  • childAddress
    • 子トークンのコレクションスマートコントラクトのアドレス。指定の親トークンの子トークン配列の指定インデックスに存在することが期待されます。
  • childId
    • 親トークンの子トークン配列の指定インデックスに存在することが期待される子トークンのID。
  • isPending
    • 保留中の子トークンから転送された場合 (true) またはアクティブな子トークンから転送された場合 (false) を示すブール値。
  • data
    • 指定されたフォーマットのない追加データ。addChild 呼び出しで送信されます。

_beforeRejectAllChildren

javascript
function _beforeRejectAllChildren(uint256 tokenId) internal virtual {}

概要 特定のトークンの保留中の子トークン配列がクリアされる前に呼び出されるフック。 保留中の子トークンのクリア前の特定の処理をカスタマイズするために使用できます。

詳細

  • 特定のトークンの保留中の子トークン配列がクリアされる前に呼び出されます。
  • 保留中の子トークンのクリア前の処理をカスタマイズするために使用できます。
  • tokenIdが提供されます。

引数

  • tokenId
    • 保留中の子トークンをすべて拒否するトークンのID。

_afterRejectAllChildren

javascript
function _afterRejectAllChildren(uint256 tokenId) internal virtual {}

概要 特定のトークンの保留中の子トークン配列がクリアされた後に呼び出されるフック。 保留中の子トークンのクリア後の特定の処理をカスタマイズするために使用できます。

詳細

  • 特定のトークンの保留中の子トークン配列がクリアされた後に呼び出されます。
  • 保留中の子トークンのクリア後の処理をカスタマイズするために使用できます。
  • tokenIdが提供されます。

引数

  • tokenId
    • 保留中の子トークンをすべて拒否したトークンのID。

セキュリティ考慮事項

この規格には、ERC721(非代替トークン規格)と同様のセキュリティ上の注意事項が適用されます。 この注意事項には、スマートコントラクト内の各種関数(たとえば、burn、add child、accept childなど)に非表示のロジックが組み込まれている可能性があることが含まれます。 したがって、この規格を実装する際には、慎重にセキュリティを確保する必要があります。

この規格では、トークンの現在の所有者がトークンを管理できるように設計されています。 これは所有者に柔軟性を提供しますが、同時に特定のリスクも伴います。 例えば、親トークンが販売リストに掲載された後、売り手が販売前に子トークンを削除する可能性があります。 その結果、購入者が期待通りの子トークンを受け取らない可能性があるため、この規格を使用する場合、マーケットプレイスは検証手段を提供するか、悪意のある行動に対処する方法を実装する必要があります。

さらに、この規格ではbalanceOfメソッドが提供されていますが、このメソッドは指定したアドレスが直接所有するトークンの数を数えるためのものです。 ただし、このメソッドは、アドレスが所有するトークン内にさらにネストされたトークンを考慮に入れません。 このため、深いネストのトークンを正確にカウントするためには、再帰的な検索が必要であり、メソッドの性能に悪影響を与える可能性があります。

引用

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

最後に

今回は「コントラクトで使用している時間表現を判別できる規格を提案しているERC6372」についてまとめてきました! いかがだったでしょうか?

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

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