はじめに

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

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

今回は、オンチェーンロイヤリティを強制化する仕組みを提案しているERC721Cについてまとめていきます!

ERC721Cとは

ERC721Cとは、NFTを作成したクリエイターが確実に二次売買時のロイヤリティを受け取れるようにする拡張機能です。

ロイヤリティについては以下を参考にしてください。

NFTの標準規格であるERC721やERC1155には、ロイヤリティに関する実装は特にされていませんでした。 そのため、OpenseaなどのNFTマーケットプレイス側でNFT売買時に、その売り上げの一部をクリエイターに還元する仕組みを導入していました。 これはオンチェーン(ブロックチェーン上)での強制ロイヤリティではなく、オフチェーン(ブロックチェーン上ではない)でのロイヤリティの仕組みでした。 そこで提案されたのがERC2981です。 ERC2981は、オンチェーンでのロイヤリティ強制化の仕組みを提案している規格です。

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

しかし、ERC2981では強制力に欠けていたため、完全なるオンチェーンロイヤリティを実装することはできませんでした。

例えば、以下のような場面だとロイヤリティを受け取れなくなってしまいます。

  1. 取引手数料0のマーケットプレイス
    • マーケットプレイス側でERC2981を実装していなかったり、取引手数料を0にしていると、オンチェーンロイヤリティを強制化することはできません。
  2. 個人間取引
    • マーケットプレイスを使用せずに、個人間で取引する場合もオンチェーンロイヤリティを強制化することはできません。
    • 個人間で一定量のETHを送ってもらい、送ったユーザーに対してNFTを送付する場合にはロイヤリティを回避できてしまいます。

そこで、登場したのがERC721CおよびERC1155Cです。

ERC721CとERC1155Cは、既存のNFT標準規格を拡張して完全に後方互換性を持たせることで、オンチェーンロイヤリティを強制することができます。 ERC721CとERC1155Cを使用することで、クリエイターは自身で安全で有用だと判断するコントラクトやアプリケーションとのみやりとりを許可することができるようになります。 また、既存のNFT標準と互換性があり、すでに存在するNFTと統合して新しいNFTを作成しやすくなります。 ロイヤリティの設定についてはクリエイターに委ねられ、自身が作成したNFTに対する正当な報酬を受け取ることができます。

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

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

ERC721Cの詳細

ERC721Cの実装についてまとめていきます。

実装の手順については以下のように書かれています。

  1. コントラクトの継承。
  2. コントラクトのカスタマイズ。
  3. ERC721Cコレクションのデプロイ。
  4. セキュリティポリシーの設定。
    • デプロイ後一回だけ実行できるコンストラクタ内やsetToDefaultSecurityPolicy関数を実行。
    • これにより、ERC721CコントラクトはCreator Token Transfer Validatorコントラクトを指し示して、transferセキュリティポリシーを適用できます。
  5. 完了
    • セットアップが完了し、コレクションはトークンの送付時にtransferセキュリティポリシーを適用できます。
    • また、開発者はいつでもtransferセキュリティポリシーとホワイトリストを変更でき、Creator Token Transfer Validatorコントラクトを別のカスタム実装に変更することもできます。
javascript
import "../../contracts/erc721c/ERC721C.sol";

contract MyERC721CCollection is ERC721C {

    constructor() ERC721C("Name of My Collection", "SYMBOL") {
        setToDefaultSecurityPolicy();
    }

    // TODO: Implement/Override base uri
    // TODO: Implement public minting function(s)
    // TODO: Implement EIP-2981 Royalties
    // TODO: Your other contract features, if any
}
Operator Whitelists
  • オペレーターホワイトリストは、特定の操作(送金やデータ変更など)を許可されたアカウントのリストです。
  • 管理者がこのリストにアカウントを追加・削除できます。
  • リストに登録されたアカウントだけが、特定の操作を実行できます。
Context Receiver Allow Lists
  • コンテキストレシーバー許可リストは、特定のコントラクトからデータを受信できるアカウントまたはコントラクトのリストです。
  • 管理者はこのリストにアカウントやコントラクトを追加・削除することができます。
Sets Transfer Security Level Of Collections
  • コレクション(複数のデータやアイテムのグループ)の送金セキュリティレベルを設定する機能です。
  • 管理者がセキュリティレベルを高めることで、不正な送金を防ぐことができます。
Sets Operator Whitelist Of Collections
  • 特定のコレクションに対して、オペレーターホワイトリストを設定する機能です。
  • 管理者は特定のコレクションに対して、どのアカウントが操作できるのかを制限することができます。
Sets Contract Receiver Allow List of Collections
  • 特定のコレクションに対して、コンテキストレシーバー許可リストを設定します。
  • 管理者は特定のコレクションに対して、どのコントラクトやアカウントがデータを受け取ることができるのかを制限できます。

transferセキュリティポリシー

事前定義されたtransferセキュリティレベル

  • トークン転送のセキュリティ基準を示すレベルです。
  • 異なるレベルは、異なるセキュリティ要件を持っています。

オペレータホワイトリストID

  • このIDはホワイトリストを指すもので、特定のオペレータがトークン転送を行えるかどうかをコントロールします。

許可されたコントラクト受信者の許可リストID

  • このIDは、トークン転送を受け取ることが許可されているコントラクトのリストを指します。
  • これは任意機能で、特定のコントラクトのみがトークンを受け取ることができるように制御します。

transferセキュリティレベル

transferセキュリティポリシー内のtransferセキュリティレベルは、上記の画像に記載されているように7段階あります。

Level 0

  • 呼び出し側の制約
    • なし
  • 受け取り側の制約
    • なし
  • 説明
    • 制約なしで、任意の呼び出し側がトークンを任意の受け取り側に転送できる最も緩いレベル。

Level 1

  • 呼び出し側の制約
    • OperatorWhitelistEnableOTC
  • 受け取り側の制約
    • なし
  • 説明
    • 呼び出し側は、オペレーターまたはトークンの所有者としてホワイトリストに登録されている必要がありますが、受け取り側には制約はありません。

Level 2

  • 呼び出し側の制約
    • OperatorWhitelistDisableOTC
  • 受け取り側の制約
    • なし
  • 説明
    • 呼び出し側はホワイトリストに登録されている必要があり、OTC転送は許可されていませんが、受け取り側には制約はありません。

Level 3

  • 呼び出し側の制約
    • OperatorWhitelistEnableOTC
  • 受け取り側の制約
    • NoCode
  • 説明
    • 呼び出し側はホワイトリストに登録されている必要があり、受け取り側はスマートコントラクトであってはならないという制約があります。

Level 4

  • 呼び出し側の制約
    • OperatorWhitelistEnableOTC
  • 受け取り側の制約
    • EOA
  • 説明
    • 呼び出し側はホワイトリストに登録されている必要があり、受け取り側はEOA(Externally Owned Account)である必要があります。

Level 5

  • 呼び出し側の制約
    • OperatorWhitelistDisableOTC
  • 受け取り側の制約
    • NoCode
  • 説明
    • 呼び出し側はホワイトリストに登録され、OTC転送は許可されていません。
    • 受け取り側はスマートコントラクトであってはならないという制約があります。

Level 6

  • 呼び出し側の制約
    • OperatorWhitelistDisableOTC
  • 受け取り側の制約
    • EOA
  • 説明
    • 呼び出し側はホワイトリストに登録され、OTC転送は許可されていません。
    • 受け取り側はEOAである必要があります。

OTCは「Over-The-Counter」の略で、一般的には証券取引所を通さずに直接取引を行う方法のことです。 この提案では、直接のトークンの転送や取引のことを指します。

ERC721Cの実装

ERC721Cの実装についてまとめていきます。 ERC721Cを利用することで、ERC2981の上に新しいタイプのプログラム可能なロイヤリティコントラクトを構築できます。 これにより、クリエーターはコミュニティやパートナー、アフィリエイトとロイヤリティを共有するなど様々な実装ができます。

基本ロイヤリティ

プログラム可能なロイヤリティの最も基本的な形で、コントラクトのオーナーがデフォルトのコレクションロイヤリティまたはトークンごとのロイヤリティ設定を設定できます。 これにより、各トークンの販売から得られるロイヤリティの割合や分配をカスタマイズすることができます。

javascript
abstract contract BasicRoyalties is ERC2981 {

    event DefaultRoyaltySet(address indexed receiver, uint96 feeNumerator);
    event TokenRoyaltySet(uint256 indexed tokenId, address indexed receiver, uint96 feeNumerator);

    constructor(address receiver, uint96 feeNumerator) {
        _setDefaultRoyalty(receiver, feeNumerator);
    }

    function _setDefaultRoyalty(address receiver, uint96 feeNumerator) internal virtual override {
        super._setDefaultRoyalty(receiver, feeNumerator);
        emit DefaultRoyaltySet(receiver, feeNumerator);
    }

    function _setTokenRoyalty(uint256 tokenId, address receiver, uint96 feeNumerator) internal virtual override {
        super._setTokenRoyalty(tokenId, receiver, feeNumerator);
        emit TokenRoyaltySet(tokenId, receiver, feeNumerator);
    }
}

minterのみのロイヤリティ

個々のトークンの採掘者にロイヤリティの100%を付与します。 ロイヤリティの金額を変更することはできません。 しかし、設定次第ではminterが独自のロイヤリティレートを設定することもできます。

javascript
abstract contract ImmutableMinterRoyalties is IERC2981, ERC165 {

    error ImmutableMinterRoyalties__MinterCannotBeZeroAddress();
    error ImmutableMinterRoyalties__MinterHasAlreadyBeenAssignedToTokenId();
    error ImmutableMinterRoyalties__RoyaltyFeeWillExceedSalePrice();

    uint256 public constant FEE_DENOMINATOR = 10_000;
    uint256 public immutable royaltyFeeNumerator;

    mapping (uint256 => address) private _minters;

    constructor(uint256 royaltyFeeNumerator_) {
        if(royaltyFeeNumerator_ > FEE_DENOMINATOR) {
            revert ImmutableMinterRoyalties__RoyaltyFeeWillExceedSalePrice();
        }

        royaltyFeeNumerator = royaltyFeeNumerator_;
    }

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

    function royaltyInfo(
        uint256 tokenId,
        uint256 salePrice
    ) external view override returns (address receiver, uint256 royaltyAmount) {
        return (_minters[tokenId], (salePrice * royaltyFeeNumerator) / FEE_DENOMINATOR);
    }

    function _onMinted(address minter, uint256 tokenId) internal {
        if (minter == address(0)) {
            revert ImmutableMinterRoyalties__MinterCannotBeZeroAddress();
        }

        if (_minters[tokenId] != address(0)) {
            revert ImmutableMinterRoyalties__MinterHasAlreadyBeenAssignedToTokenId();
        }

        _minters[tokenId] = minter;
    }

    function _onBurned(uint256 tokenId) internal {
        delete _minters[tokenId];
    }
}

ロイヤリティの共有

クリエイターとトークンのminterの間でロイヤリティを共有するために、複数のアドレスにロイヤリティを支払います。

javascript
abstract contract MinterCreatorSharedRoyalties is IERC2981, ERC165 {
    error MinterCreatorSharedRoyalties__RoyaltyFeeWillExceedSalePrice();
    error MinterCreatorSharedRoyalties__MinterCannotBeZeroAddress();
    error MinterCreatorSharedRoyalties__MinterHasAlreadyBeenAssignedToTokenId();
    error MinterCreatorSharedRoyalties__PaymentSplitterDoesNotExistForSpecifiedTokenId();

    enum ReleaseTo {
        Minter,
        Creator
    }

    uint256 public constant FEE_DENOMINATOR = 10_000;
    uint256 public immutable royaltyFeeNumerator;
    uint256 public immutable minterShares;
    uint256 public immutable creatorShares;
    address public immutable creator;

    mapping (uint256 => address) private _minters;
    mapping (uint256 => address) private _paymentSplitters;
    mapping (address => address[]) private _minterPaymentSplitters;

    constructor(uint256 royaltyFeeNumerator_, uint256 minterShares_, uint256 creatorShares_, address creator_) {
        if(royaltyFeeNumerator_ > FEE_DENOMINATOR) {
            revert MinterCreatorSharedRoyalties__RoyaltyFeeWillExceedSalePrice();
        }

        royaltyFeeNumerator = royaltyFeeNumerator_;
        minterShares = minterShares_;
        creatorShares = creatorShares_;
        creator = creator_;
    }

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

    function royaltyInfo(
        uint256 tokenId,
        uint256 salePrice
    ) external view override returns (address, uint256) {
        return (_paymentSplitters[tokenId], (salePrice * royaltyFeeNumerator) / FEE_DENOMINATOR);
    }

    function minterOf(uint256 tokenId) external view returns (address) {
        return _minters[tokenId];
    }

    function paymentSplitterOf(uint256 tokenId) external view returns (address) {
        return _paymentSplitters[tokenId];
    }

    function paymentSplittersOfMinter(address minter) external view returns (address[] memory) {
        return _minterPaymentSplitters[minter];
    }

    function releasableNativeFunds(uint256 tokenId, ReleaseTo releaseTo) external view returns (uint256) {
        PaymentSplitter paymentSplitter = _getPaymentSplitterForTokenOrRevert(tokenId);

        if (releaseTo == ReleaseTo.Minter) {
            return paymentSplitter.releasable(payable(_minters[tokenId]));
        } else {
            return paymentSplitter.releasable(payable(creator));
        }
    }

    function releasableERC20Funds(uint256 tokenId, address coin, ReleaseTo releaseTo) external view returns (uint256) {
        PaymentSplitter paymentSplitter = _getPaymentSplitterForTokenOrRevert(tokenId);

        if (releaseTo == ReleaseTo.Minter) {
            return paymentSplitter.releasable(IERC20(coin), _minters[tokenId]);
        } else {
            return paymentSplitter.releasable(IERC20(coin), creator);
        }
    }

    function releaseNativeFunds(uint256 tokenId, ReleaseTo releaseTo) external {
        PaymentSplitter paymentSplitter = _getPaymentSplitterForTokenOrRevert(tokenId);

        if (releaseTo == ReleaseTo.Minter) {
            paymentSplitter.release(payable(_minters[tokenId]));
        } else {
            paymentSplitter.release(payable(creator));
        }
    }

    function releaseERC20Funds(uint256 tokenId, address coin, ReleaseTo releaseTo) external {
        PaymentSplitter paymentSplitter = _getPaymentSplitterForTokenOrRevert(tokenId);

        if(releaseTo == ReleaseTo.Minter) {
            paymentSplitter.release(IERC20(coin), _minters[tokenId]);
        } else {
            paymentSplitter.release(IERC20(coin), creator);
        }
    }

    function _onMinted(address minter, uint256 tokenId) internal {
        if (minter == address(0)) {
            revert MinterCreatorSharedRoyalties__MinterCannotBeZeroAddress();
        }

        if (_minters[tokenId] != address(0)) {
            revert MinterCreatorSharedRoyalties__MinterHasAlreadyBeenAssignedToTokenId();
        }

        address paymentSplitter = _createPaymentSplitter(minter);
        _paymentSplitters[tokenId] = paymentSplitter;
        _minterPaymentSplitters[minter].push(paymentSplitter);
        _minters[tokenId] = minter;
    }

    function _onBurned(uint256 tokenId) internal {
        delete _paymentSplitters[tokenId];
        delete _minters[tokenId];
    }

    function _createPaymentSplitter(address minter) private returns (address) {
        if (minter == creator) {
            address[] memory payees = new address[](1);
            payees[0] = creator;

            uint256[] memory shares = new uint256[](1);
            shares[0] = minterShares + creatorShares;

            return address(new PaymentSplitter(payees, shares));
        } else {
            address[] memory payees = new address[](2);
            payees[0] = minter;
            payees[1] = creator;

            uint256[] memory shares = new uint256[](2);
            shares[0] = minterShares;
            shares[1] = creatorShares;

            return address(new PaymentSplitter(payees, shares));
        }
    }

    function _getPaymentSplitterForTokenOrRevert(uint256 tokenId) private view returns (PaymentSplitter) {
        address paymentSplitterForToken = _paymentSplitters[tokenId];
        if(paymentSplitterForToken == address(0)) {
            revert MinterCreatorSharedRoyalties__PaymentSplitterDoesNotExistForSpecifiedTokenId();
        }

        return PaymentSplitter(payable(paymentSplitterForToken));
    }
}

譲渡可能なロイヤリティ

ロイヤリティの所有権を表すために、セカンダリNFTを使用する。 minterは最初にロイヤリティの権利をNFTで受け取りますが、その権利は他のウォレットに譲渡することができます。

javascript
interface ICloneableRoyaltyRightsERC721 is IERC721 {
    function initializeAndBindToCollection() external;
    function mint(address to, uint256 tokenId) external;
    function burn(uint256 tokenId) external;
}

abstract contract MinterRoyaltiesReassignableRightsNFT is IERC2981, ERC165 {

    error MinterRoyaltiesReassignableRightsNFT__MinterCannotBeZeroAddress();
    error MinterRoyaltiesReassignableRightsNFT__RoyaltyFeeWillExceedSalePrice();

    uint256 public constant FEE_DENOMINATOR = 10_000;
    uint256 public immutable royaltyFeeNumerator;
    ICloneableRoyaltyRightsERC721 public immutable royaltyRightsNFT;

    constructor(uint256 royaltyFeeNumerator_, address royaltyRightsNFTReference_) {
        if(royaltyFeeNumerator_ > FEE_DENOMINATOR) {
            revert MinterRoyaltiesReassignableRightsNFT__RoyaltyFeeWillExceedSalePrice();
        }

        royaltyFeeNumerator = royaltyFeeNumerator_;


        ICloneableRoyaltyRightsERC721 royaltyRightsNFT_ = 
            ICloneableRoyaltyRightsERC721(Clones.clone(royaltyRightsNFTReference_));
        royaltyRightsNFT_.initializeAndBindToCollection();

        royaltyRightsNFT = royaltyRightsNFT_;
    }

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

    function royaltyInfo(
        uint256 tokenId,
        uint256 salePrice
    ) external view override returns (address receiver, uint256 royaltyAmount) {

        address rightsHolder = address(0);

        try royaltyRightsNFT.ownerOf(tokenId) returns (address rightsTokenOwner) {
            rightsHolder = rightsTokenOwner;
        } catch {}

        return (rightsHolder, (salePrice * royaltyFeeNumerator) / FEE_DENOMINATOR);
    }

    function _onMinted(address minter, uint256 tokenId) internal {
        if (minter == address(0)) {
            revert MinterRoyaltiesReassignableRightsNFT__MinterCannotBeZeroAddress();
        }

        royaltyRightsNFT.mint(minter, tokenId);
    }

    function _onBurned(uint256 tokenId) internal {
        royaltyRightsNFT.burn(tokenId);
    }
}

contract RoyaltyRightsNFT is ERC721, ICloneableRoyaltyRightsERC721 {

    error RoyaltyRightsNFT__CollectionAlreadyInitialized();
    error RoyaltyRightsNFT__OnlyBurnableFromCollection();
    error RoyaltyRightsNFT__OnlyMintableFromCollection();

    IERC721Metadata public collection;

    constructor() ERC721("", "") {}

    function initializeAndBindToCollection() external override {
        if (address(collection) != address(0)) {
            revert RoyaltyRightsNFT__CollectionAlreadyInitialized();
        }

        collection = IERC721Metadata(_msgSender());
    }

    function mint(address to, uint256 tokenId) external override {
        if (_msgSender() != address(collection)) {
            revert RoyaltyRightsNFT__OnlyMintableFromCollection();
        }

        _mint(to, tokenId);
    }

    function burn(uint256 tokenId) external override {
        if (_msgSender() != address(collection)) {
            revert RoyaltyRightsNFT__OnlyBurnableFromCollection();
        }

        _burn(tokenId);
    }

    function name() public view virtual override returns (string memory) {
        return string(abi.encodePacked(collection.name(), " Royalty Rights"));
    }

    function symbol() public view virtual override returns (string memory) {
        return string(abi.encodePacked(collection.symbol(), "RR"));
    }

    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
        return collection.tokenURI(tokenId);
    }
}

ミックス

様々な拡張機能を組み合わせたERC721Cのコントラクトです。

javascript
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "@limitbreak/creator-token-contracts/contracts
/erc721c/ERC721C.sol";
import "@limitbreak/creator-token-contracts/contracts
/programmable-royalties/ImmutableMinterRoyalties.sol";

contract ERC721CWithImmutableMinterRoyalties is ERC721C, ImmutableMinterRoyalties {

    constructor(
        uint256 royaltyFeeNumerator_,
        string memory name_,
        string memory symbol_) 
        ERC721C(name_, symbol_) 
        ImmutableMinterRoyalties(royaltyFeeNumerator_) {
        setToDefaultSecurityPolicy();
    }

    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721C, ImmutableMinterRoyalties) returns (bool) {
        return super.supportsInterface(interfaceId);
    }

    function mint(address to, uint256 tokenId) external {
        _mint(to, tokenId);
    }

    function safeMint(address to, uint256 tokenId) external {
        _safeMint(to, tokenId);
    }

    function burn(uint256 tokenId) external {
        _burn(tokenId);
    }

    function _mint(address to, uint256 tokenId) internal virtual override {
        _onMinted(to, tokenId);
        super._mint(to, tokenId);
    }

    function _burn(uint256 tokenId) internal virtual override {
        super._burn(tokenId);
        _onBurned(tokenId);
    }
}

参考実装

最後に参考実装を見ていきましょう。

ERC721C.sol

以下がERC721Cのコントラクトになります。

javascript
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "../utils/CreatorTokenBase.sol";
import "../token/erc721/ERC721OpenZeppelin.sol";

/**
 * @title ERC721C
 * @author Limit Break, Inc.
 * @notice Extends OpenZeppelin's ERC721 implementation with Creator Token functionality, which
 *         allows the contract owner to update the transfer validation logic by managing a security policy in
 *         an external transfer validation security policy registry.  See {CreatorTokenTransferValidator}.
 */
abstract contract ERC721C is ERC721OpenZeppelin, CreatorTokenBase {

    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(ICreatorToken).interfaceId || super.supportsInterface(interfaceId);
    }

    /// @dev Ties the open-zeppelin _beforeTokenTransfer hook to more granular transfer validation logic
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 firstTokenId,
        uint256 batchSize) internal virtual override {
        for (uint256 i = 0; i < batchSize;) {
            _validateBeforeTransfer(from, to, firstTokenId + i);
            unchecked {
                ++i;
            }
        }
    }

    /// @dev Ties the open-zeppelin _afterTokenTransfer hook to more granular transfer validation logic
    function _afterTokenTransfer(
        address from,
        address to,
        uint256 firstTokenId,
        uint256 batchSize) internal virtual override {
        for (uint256 i = 0; i < batchSize;) {
            _validateAfterTransfer(from, to, firstTokenId + i);
            unchecked {
                ++i;
            }
        }
    }
}

/**
 * @title ERC721CInitializable
 * @author Limit Break, Inc.
 * @notice Initializable implementation of ERC721C to allow for EIP-1167 proxy clones.
 */
abstract contract ERC721CInitializable is ERC721OpenZeppelinInitializable, CreatorTokenBase {
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(ICreatorToken).interfaceId || super.supportsInterface(interfaceId);
    }

    /// @dev Ties the open-zeppelin _beforeTokenTransfer hook to more granular transfer validation logic
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 firstTokenId,
        uint256 batchSize) internal virtual override {
        for (uint256 i = 0; i < batchSize;) {
            _validateBeforeTransfer(from, to, firstTokenId + i);
            unchecked {
                ++i;
            }
        }
    }

    /// @dev Ties the open-zeppelin _afterTokenTransfer hook to more granular transfer validation logic
    function _afterTokenTransfer(
        address from,
        address to,
        uint256 firstTokenId,
        uint256 batchSize) internal virtual override {
        for (uint256 i = 0; i < batchSize;) {
            _validateAfterTransfer(from, to, firstTokenId + i);
            unchecked {
                ++i;
            }
        }
    }
}

transfer関数の処理の実行前後で呼ばれる、_beforeTokenTransferと_afterTokenTransferを上書きして、各トランザクションごとに以下の関数を実行しているのが確認できます。

javascript
_validateAfterTransfer(from, to, firstTokenId + i);

上記関数は以下で実装されています。

_validateBeforeTransfer

javascript
function _validateBeforeTransfer(address from, address to, uint256 tokenId) internal virtual {
    bool fromZeroAddress = from == address(0);
    bool toZeroAddress = to == address(0);

    if(fromZeroAddress && toZeroAddress) {
        revert ShouldNotMintToBurnAddress();
    } else if(fromZeroAddress) {
        _preValidateMint(_msgSender(), to, tokenId, msg.value);
    } else if(toZeroAddress) {
        _preValidateBurn(_msgSender(), from, tokenId, msg.value);
    } else {
        _preValidateTransfer(_msgSender(), from, to, tokenId, msg.value);
    }
}

概要 継承元のコントラクトが、_beforeTokenTransfer関数内でより詳細なフックを取得するために呼び出す必要がある関数。

詳細

  • トークンのトランスファーが発生する前に実行され、トークンのトランスファーに関する詳細な検証を行います。
  • 送信元アドレスがゼロアドレスであり、受信先アドレスもゼロアドレスの場合、トークンをミントしてバーンアドレスに送金することはできません(ShouldNotMintToBurnAddress例外をスロー)。
  • 送信元アドレスがゼロアドレスであり、受信先アドレスが通常のアドレスの場合、トークンのミントを事前に検証します。
  • 送信元アドレスが通常のアドレスであり、受信先アドレスがゼロアドレスの場合、トークンのバーンを事前に検証します。
  • 送信元アドレスと受信先アドレスが通常のアドレスの場合、通常のトークンのトランスファーを事前に検証します。

引数

  • from
    • 送信元のアドレス。
  • to
    • 受信先のアドレス。
  • tokenId
    • トークンID。

_validateAfterTransfer

javascript
function _validateAfterTransfer(address from, address to, uint256 tokenId) internal virtual {
    bool fromZeroAddress = from == address(0);
    bool toZeroAddress = to == address(0);

    if(fromZeroAddress && toZeroAddress) {
        revert ShouldNotMintToBurnAddress();
    } else if(fromZeroAddress) {
        _postValidateMint(_msgSender(), to, tokenId, msg.value);
    } else if(toZeroAddress) {
        _postValidateBurn(_msgSender(), from, tokenId, msg.value);
    } else {
        _postValidateTransfer(_msgSender(), from, to, tokenId, msg.value);
    }
}

概要 継承元のコントラクトが、_afterTokenTransfer関数内でより詳細なフックを取得するために呼び出す必要がある関数。

詳細

  • トークンのトランスファーが完了した後に実行され、トークンのトランスファーに関する詳細な検証を行います。
  • 送信元アドレスがゼロアドレスであり、受信先アドレスもゼロアドレスの場合、トークンをミントしてバーンアドレスに送金することはできません(ShouldNotMintToBurnAddress例外をスロー)。
  • 送信元アドレスがゼロアドレスであり、受信先アドレスが通常のアドレスの場合、トークンのミントを事後に検証します。
  • 送信元アドレスが通常のアドレスであり、受信先アドレスがゼロアドレスの場合、トークンのバーンを事後に検証します。
  • 送信元アドレスと受信先アドレスが通常のアドレスの場合、通常のトークンのトランスファーを事後に検証します。

引数

  • from
    • 送信元のアドレス。
  • to
    • 受信先のアドレス。
  • tokenId
    • トークンID。

条件分岐後に実行される各関数では、以下のようにそれぞれ好きなように設定できます。

javascript
/// @dev Optional validation hook that fires before a mint
function _preValidateMint(address caller, address to, uint256 tokenId, uint256 value) internal virtual {}

/// @dev Optional validation hook that fires after a mint
function _postValidateMint(address caller, address to, uint256 tokenId, uint256 value) internal virtual {}

/// @dev Optional validation hook that fires before a burn
function _preValidateBurn(address caller, address from, uint256 tokenId, uint256 value) internal virtual {}

/// @dev Optional validation hook that fires after a burn
function _postValidateBurn(address caller, address from, uint256 tokenId, uint256 value) internal virtual {}

/// @dev Optional validation hook that fires before a transfer
function _preValidateTransfer(address caller, address from, address to, uint256 tokenId, uint256 value) internal virtual {}

/// @dev Optional validation hook that fires after a transfer
function _postValidateTransfer(address caller, address from, address to, uint256 tokenId, uint256 value) internal virtual {}

ERC721OpenZeppelin.sol

javascript
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.4;

import "../../access/OwnablePermissions.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";

abstract contract ERC721OpenZeppelinBase is ERC721 {

    // Token name
    string internal _contractName;

    // Token symbol
    string internal _contractSymbol;

    function name() public view virtual override returns (string memory) {
        return _contractName;
    }

    function symbol() public view virtual override returns (string memory) {
        return _contractSymbol;
    }

    function _setNameAndSymbol(string memory name_, string memory symbol_) internal {
        _contractName = name_;
        _contractSymbol = symbol_;
    }
}

abstract contract ERC721OpenZeppelin is ERC721OpenZeppelinBase {
    constructor(string memory name_, string memory symbol_) ERC721("", "") {
        _setNameAndSymbol(name_, symbol_);
    }
}

abstract contract ERC721OpenZeppelinInitializable is OwnablePermissions, ERC721OpenZeppelinBase {

    error ERC721OpenZeppelinInitializable__AlreadyInitializedERC721();

    /// @notice Specifies whether or not the contract is initialized
    bool private _erc721Initialized;

    /// @dev Initializes parameters of ERC721 tokens.
    /// These cannot be set in the constructor because this contract is optionally compatible with EIP-1167.
    function initializeERC721(string memory name_, string memory symbol_) public {
        _requireCallerIsContractOwner();

        if(_erc721Initialized) {
            revert ERC721OpenZeppelinInitializable__AlreadyInitializedERC721();
        }

        _erc721Initialized = true;

        _setNameAndSymbol(name_, symbol_);
    }
}

基本的なOpenzeppelinでのERC721の実装ですね。

CreatorTokenBase.sol

ここでtransferセキュリティポリシーを色々設定していますね。

DEFAULT_TRANSFER_VALIDATOR

javascript
address public constant DEFAULT_TRANSFER_VALIDATOR = address(0x0000721C310194CcfC01E523fc93C9cCcFa2A0Ac);

概要 デフォルトのトランスファーバリデータのアドレスを示す定数。

詳細 この定数は、トークンのトランスファーに関するバリデーションを行うコントラクトのデフォルトのアドレスを指定します。 トランスファーバリデータは、トークンの移動に関するセキュリティポリシーを管理します。

デフォルトでは0x0000721C310194CcfC01E523fc93C9cCcFa2A0Acが指定されています。


DEFAULT_TRANSFER_SECURITY_LEVEL

javascript
TransferSecurityLevels public constant DEFAULT_TRANSFER_SECURITY_LEVEL = TransferSecurityLevels.One;

概要 デフォルトのトランスファーセキュリティレベルを示す定数。

詳細 この定数は、トークンのトランスファーセキュリティポリシーのデフォルトのセキュリティレベルを指定します。 セキュリティレベルは、トランスファーに関する制限を設定するために使用されます。

パラメータ

  • なし

DEFAULT_OPERATOR_WHITELIST_ID

javascript
uint120 public constant DEFAULT_OPERATOR_WHITELIST_ID = uint120(1);

概要 デフォルトのオペレータホワイトリストIDを示す定数。

詳細 この定数は、トークンのトランスファーセキュリティポリシーにおけるデフォルトのオペレータホワイトリストのIDを指定します。 オペレータホワイトリストは、特定のアドレスがトークンを操作できるかどうかを管理します。


transferValidator

javascript
ICreatorTokenTransferValidator private transferValidator;

概要 トークンのトランスファーバリデータのインターフェースを実装するプライベート変数。

詳細 この変数は、トークンのトランスファーバリデーションを管理するために使用されます。 トークンのセキュリティポリシーを定義し、トランスファーを許可または拒否するためにトランスファーバリデータとのやり取りを行います。


setToDefaultSecurityPolicy

javascript
function setToDefaultSecurityPolicy() public virtual {
    _requireCallerIsContractOwner();
    setTransferValidator(DEFAULT_TRANSFER_VALIDATOR);
    ICreatorTokenTransferValidator(DEFAULT_TRANSFER_VALIDATOR).setTransferSecurityLevelOfCollection(address(this), DEFAULT_TRANSFER_SECURITY_LEVEL);
    ICreatorTokenTransferValidator(DEFAULT_TRANSFER_VALIDATOR).setOperatorWhitelistOfCollection(address(this), DEFAULT_OPERATOR_WHITELIST_ID);
}

概要 コントラクトの所有者がトランスファーバリデータを公式のバリデータコントラクトに設定し、推奨されるデフォルトのセキュリティポリシーを設定する関数。

詳細

  • この関数は個別のコレクションのデフォルトの動作を変更するためにオーバーライドすることができます。
  • _requireCallerIsContractOwner関数を呼び出して、関数の呼び出し元がコントラクトの所有者であることを確認します。
  • setTransferValidator関数を呼び出して、トランスファーバリデータを指定のデフォルトバリデータに設定します。
  • ICreatorTokenTransferValidatorインターフェースを使用して、セキュリティレベルとオペレータホワイトリストの設定をデフォルト値に設定します。

setToCustomValidatorAndSecurityPolicy

javascript
function setToCustomValidatorAndSecurityPolicy(
    address validator, 
    TransferSecurityLevels level, 
    uint120 operatorWhitelistId, 
    uint120 permittedContractReceiversAllowlistId) public {
    _requireCallerIsContractOwner();

    setTransferValidator(validator);

    ICreatorTokenTransferValidator(validator).
        setTransferSecurityLevelOfCollection(address(this), level);

    ICreatorTokenTransferValidator(validator).
        setOperatorWhitelistOfCollection(address(this), operatorWhitelistId);

    ICreatorTokenTransferValidator(validator).
        setPermittedContractReceiverAllowlistOfCollection(address(this), permittedContractReceiversAllowlistId);
}

概要 コントラクトの所有者がトランスファーバリデータをカスタムバリデータコントラクトに設定し、カスタムのセキュリティポリシーを設定する関数。 詳細

  • カスタムバリデータコントラクトのアドレス、トランスファーセキュリティレベル、オペレータホワイトリストのID、許可されたコントラクトレシーバーホワイトリストのIDを指定することができます。
  • _requireCallerIsContractOwner関数を呼び出して、関数の呼び出し元がコントラクトの所有者であることを確認します。
  • setTransferValidator関数を呼び出して、指定されたカスタムバリデータコントラクトにトランスファーバリデータを設定します。
  • ICreatorTokenTransferValidatorインターフェースを使用して、セキュリティレベル、オペレータホワイトリスト、許可されたコントラクトレシーバーホワイトリストの設定をカスタム値に設定します。

引数

  • validator
    • カスタムバリデータコントラクトのアドレス。
  • level
    • トランスファーセキュリティレベル。
  • operatorWhitelistId
    • オペレータホワイトリストのID。
  • permittedContractReceiversAllowlistId
    • 許可されたコントラクトレシーバーホワイトリストのID。

setToCustomSecurityPolicy

javascript
function setToCustomSecurityPolicy(
    TransferSecurityLevels level, 
    uint120 operatorWhitelistId, 
    uint120 permittedContractReceiversAllowlistId) public {
    _requireCallerIsContractOwner();

    ICreatorTokenTransferValidator validator = getTransferValidator();
    if (address(validator) == address(0)) {
        revert CreatorTokenBase__SetTransferValidatorFirst();
    }

    validator.setTransferSecurityLevelOfCollection(address(this), level);
    validator.setOperatorWhitelistOfCollection(address(this), operatorWhitelistId);
    validator.setPermittedContractReceiverAllowlistOfCollection(address(this), permittedContractReceiversAllowlistId);
}

概要 コントラクトの所有者がカスタムのセキュリティポリシーを設定する関数。

詳細

  • _requireCallerIsContractOwner関数を呼び出して、関数の呼び出し元がコントラクトの所有者であることを確認します。
  • getTransferValidator関数を呼び出して、トランスファーバリデータを取得します。
    • トランスファーバリデータが設定されていない場合はrevertします。
  • 取得したトランスファーバリデータを使用して、セキュリティレベル、オペレータホワイトリスト、許可されたコントラクトレシーバーホワイトリストをカスタムの値に設定します。
  • トランスファーバリデータが設定されていない場合はrevertします。

引数

  • level
    • トランスファーセキュリティレベル。
  • operatorWhitelistId
    • オペレータホワイトリストのID。
  • permittedContractReceiversAllowlistId
    • 許可されたコントラクトレシーバーホワイトリストのID。

setTransferValidator

javascript
function setTransferValidator(address transferValidator_) public {
    _requireCallerIsContractOwner();

    bool isValidTransferValidator = false;

    if (transferValidator_.code.length > 0) {
        try IERC165(transferValidator_).supportsInterface(type(ICreatorTokenTransferValidator).interfaceId) 
            returns (bool supportsInterface) {
            isValidTransferValidator = supportsInterface;
        } catch {}
    }

    if (transferValidator_ != address(0) && !isValidTransferValidator) {
        revert CreatorTokenBase__InvalidTransferValidatorContract();
    }

    emit TransferValidatorUpdated(address(transferValidator), transferValidator_);

    transferValidator = ICreatorTokenTransferValidator(transferValidator_);
}

概要 トークンコントラクトのトランスファーバリデータを設定する関数。

詳細

  • _requireCallerIsContractOwner関数を呼び出して、関数の呼び出し元がコントラクトの所有者であることを確認します。
  • 提供されたトランスファーバリデータコントラクトが正当なものかどうかを確認し、正当でない場合はエラーを発生させます。
  • トランスファーバリデータをアップデートし、TransferValidatorUpdatedイベントを発行します。
  • 提供されたバリデータコントラクトがゼロアドレスでなく、ICreatorTokenTransferValidatorインターフェースをサポートしていない場合にエラーが発生します。
  • 関数呼び出し元がコントラクトの所有者でない場合にもエラーが発生します。

引数

  • transferValidator_
    • トランスファーバリデータコントラクトのアドレス。

getTransferValidator

javascript
function getTransferValidator() public view override returns (ICreatorTokenTransferValidator) {
    return transferValidator;
}

概要 このトークンコントラクトのトランスファーバリデータコントラクトのアドレスを返す関数。

詳細

  • この関数は読み取り専用で、トークンコントラクトのトランスファーバリデータコントラクトのアドレスを返します。

戻り値

  • ICreatorTokenTransferValidator
    • トランスファーバリデータコントラクトのインターフェース。

getSecurityPolicy

javascript
function getSecurityPolicy() public view override returns (CollectionSecurityPolicy memory) {
    if (address(transferValidator) != address(0)) {
        return transferValidator.getCollectionSecurityPolicy(address(this));
    }

    return CollectionSecurityPolicy({
        transferSecurityLevel: TransferSecurityLevels.Zero,
        operatorWhitelistId: 0,
        permittedContractReceiversId: 0
    });
}

概要 このトークンコントラクトのセキュリティポリシーを返す関数。

詳細

  • トランスファーバリデータが設定されている場合、そのバリデータからセキュリティポリシーを取得します。
  • トランスファーバリデータが設定されていない場合、デフォルトのセキュリティポリシーを返します。
  • セキュリティポリシーには、トランスファーセキュリティレベル、オペレータホワイトリストID、許可されたコントラクトレシーバーホワイトリストIDが含まれます。

戻り値

  • CollectionSecurityPolicy
    • セキュリティポリシーを表す構造体。

getWhitelistedOperators

javascript
function getWhitelistedOperators() public view override returns (address[] memory) {
    if (address(transferValidator) != address(0)) {
        return transferValidator.getWhitelistedOperators(
            transferValidator.getCollectionSecurityPolicy(address(this)).operatorWhitelistId);
    }

    return new address[](0);
}

概要 このトークンコントラクトのホワイトリストに登録されたすべてのオペレータのリストを返す関数。

詳細

  • トランスファーバリデータが設定されている場合、バリデータからオペレータホワイトリストのIDを取得し、それを使用してホワイトリストに登録されたオペレータのリストを取得します。
  • トランスファーバリデータが設定されていない場合、空のアドレスリストを返します。
  • 高コストな呼び出しとなるため、view関数でのみ使用すべきです。

戻り値

  • address[]
    • ホワイトリストに登録されたオペレータのアドレスのリスト。

getPermittedContractReceivers

javascript
function getPermittedContractReceivers() public view override returns (address[] memory) {
    if (address(transferValidator) != address(0)) {
        return transferValidator.getPermittedContractReceivers(
            transferValidator.getCollectionSecurityPolicy(address(this)).permittedContractReceiversId);
    }

    return new address[](0);
}

概要 このトークンコントラクトに許可されたコントラクトレシーバーのリストを返す関数。

詳細

  • トランスファーバリデータが設定されている場合、バリデータから許可されたコントラクトレシーバーホワイトリストのIDを取得し、それを使用して許可されたコントラクトレシーバーのリストを取得します。
  • トランスファーバリデータが設定されていない場合、空のアドレスリストを返します。
  • 高コストな呼び出しとなるため、view関数でのみ使用すべきです。

戻り値

  • address[]
    • 許可されたコントラクトレシーバーのアドレスのリスト。

isOperatorWhitelisted

javascript
function isOperatorWhitelisted(address operator) public view override returns (bool) {
    if (address(transferValidator) != address(0)) {
        return transferValidator.isOperatorWhitelisted(
            transferValidator.getCollectionSecurityPolicy(address(this)).operatorWhitelistId, operator);
    }

    return false;
}

概要 このトークンコントラクトに対してオペレータがホワイトリストに登録されているか確認する関数。

詳細

  • トランスファーバリデータが設定されている場合、バリデータからオペレータホワイトリストのIDを取得し、指定されたオペレータがホワイトリストに登録されているか確認します。
  • トランスファーバリデータが設定されていない場合、falseを返します。

引数

  • operator
    • 確認するオペレータのアドレス。

戻り値

  • bool
    • オペレータがホワイトリストに登録されている場合はtrue、それ以外の場合はfalse。

isContractReceiverPermitted

javascript
function isContractReceiverPermitted(address receiver) public view override returns (bool) {
    if (address(transferValidator) != address(0)) {
        return transferValidator.isContractReceiverPermitted(
            transferValidator.getCollectionSecurityPolicy(address(this)).permittedContractReceiversId, receiver);
    }

    return false;
}

概要 このトークンコントラクトに対してコントラクトレシーバーが許可されているか確認する関数。

詳細

  • トランスファーバリデータが設定されている場合、バリデータから許可されたコントラクトレシーバーホワイトリストのIDを取得し、指定されたレシーバーが許可されているか確認します。
  • トランスファーバリデータが設定されていない場合、falseを返します。

引数

  • receiver
    • 確認するレシーバーのアドレス。

戻り値

  • bool
    • コントラクトレシーバーが許可されている場合はtrue、それ以外の場合はfalse。

isTransferAllowed

javascript
function isTransferAllowed(address caller, address from, address to) public view override returns (bool) {
    if (address(transferValidator) != address(0)) {
        try transferValidator.applyCollectionTransferPolicy(caller, from, to) {
            return true;
        } catch {
            return false;
        }
    }
    return true;
}

概要 トークンコントラクトのセキュリティポリシーに基づいてトランスファーが許可されているかどうかを判断する関数。

詳細

  • トランスファーバリデータが設定されている場合、バリデータを使用してトランスファーが許可されているかどうかを確認します。
    • エラーが発生した場合、トランスファーは許可されていないと見なします。
  • トランスファーバリデータが設定されていない場合、トランスファーは常に許可されていると見なします。
  • 指定されたcallerがfromアドレスからtoアドレスへのトランスファーが、このトークンのセキュリティポリシーによって許可されるかどうかをシミュレートするために使用します。

引数

  • caller
    • シミュレートされた呼び出し元のアドレス。
  • from
    • 送信者のアドレス。
  • to
    • 受信者のアドレス。

戻り値

  • bool
    • トランスファーが許可されている場合はtrue、それ以外の場合はfalse。

_preValidateTransfer

javascript
function _preValidateTransfer(
    address caller, 
    address from, 
    address to, 
    uint256 /*tokenId*/, 
    uint256 /*value*/) internal virtual override {
    if (address(transferValidator) != address(0)) {
        transferValidator.applyCollectionTransferPolicy(caller, from, to);
    }
}

概要 トークンのトランスファーを事前に検証し、トランスファーがこのトークンのセキュリティポリシーによって許可されているか検証する関数。

詳細

  • トランスファーバリデータが設定されている場合、バリデータを使用してトランスファーが許可されているかどうかを検証します。
    • トランスファーが許可されていない場合、例外を投げます。
  • トランスファーバリデータが設定されていない場合、検証は行われずトランスファーは許可されていると見なされます。
  • 継承元のコントラクトは、_beforeTokenTransfer関数またはそれに相当する関数をオーバーライドし、_validateBeforeTransferを呼び出す責任があります。

引数

  • caller
    • 呼び出し元のアドレス。
  • from
    • 送信者のアドレス。
  • to
    • 受信者のアドレス。

CreatorTokenTransferValidator

ERC721Cのコアな機能がCreatorTokenTransferValidatorコントラクトについてみていきます。

DEFAULT_ACCESS_CONTROL_ADMIN_ROLE

javascript
bytes32 private constant DEFAULT_ACCESS_CONTROL_ADMIN_ROLE = 0x00;

概要 デフォルトのアクセスコントロールの管理者ロールを表すバイト列。

詳細 コントラクト内でアクセスコントロールの管理者として使用されます。 アクセスコントロールに関連する操作を制御するために使用されます。


DEFAULT_TRANSFER_SECURITY_LEVEL

javascript
TransferSecurityLevels public constant DEFAULT_TRANSFER_SECURITY_LEVEL = TransferSecurityLevels.Zero;

概要 デフォルトのトークン転送のセキュリティレベルを表す列挙型。

詳細 トークンの転送操作に関連するセキュリティポリシーを制御するために使用されます。 デフォルトのセキュリティレベルはZeroですが、必要に応じて変更できます。


lastOperatorWhitelistId

javascript
uint120 private lastOperatorWhitelistId;

概要 最後に使用されたオペレーターホワイトリストのIDを格納する変数。

詳細 コントラクト内でオペレーターホワイトリストのIDを一意に識別するために使用されます。 新しいオペレーターホワイトリストが作成されるたびに、この変数の値が増加します。


lastPermittedContractReceiverAllowlistId

javascript
uint120 private lastPermittedContractReceiverAllowlistId;

概要 最後に使用された許可されたコントラクトレシーバーのホワイトリストのIDを格納する変数。

詳細 コントラクト内で許可されたコントラクトレシーバーのホワイトリストのIDを一意に識別するために使用されます。 新しいホワイトリストが作成されるたびに、この変数の値が増加します。


transferSecurityPolicies

javascript
mapping (TransferSecurityLevels => TransferSecurityPolicy) public transferSecurityPolicies;

概要 トークンの転送セキュリティポリシーをTransferSecurityLevelsに関連付ける配列。

詳細 各セキュリティレベルに対するトークンの転送セキュリティポリシーを格納します。 セキュリティポリシーはTransferSecurityLevels列挙型の値に関連付けられます。


collectionSecurityPolicies

javascript
mapping (address => CollectionSecurityPolicy) private collectionSecurityPolicies;

概要 各コレクション(トークンコントラクト)に対するセキュリティポリシーをコレクションのアドレスに関連付ける配列。

詳細 各トークンコレクションに対するセキュリティポリシーを格納します。 コレクションのアドレスに関連付けられたセキュリティポリシーを取得するために使用されます。


operatorWhitelistOwners

javascript
mapping (uint120 => address) public operatorWhitelistOwners;

概要 オペレーターホワイトリストの所有者をオペレーターホワイトリストIDに関連付ける配列。

詳細 各オペレーターホワイトリストIDに対する所有者のアドレスを格納します。 オペレーターホワイトリストの所有者を識別するために使用されます。


permittedContractReceiverAllowlistOwners

javascript
mapping (uint120 => address) public permittedContractReceiverAllowlistOwners;

概要 許可されたコントラクトレシーバーのホワイトリストの所有者をホワイトリストIDに関連付ける配列。

詳細 各許可されたコントラクトレシーバーのホワイトリストIDに対する所有者のアドレスを格納します。 ホワイトリストの所有者を識別するために使用されます。


operatorWhitelists

javascript
mapping (uint120 => EnumerableSet.AddressSet) private operatorWhitelists;

概要 オペレーターホワイトリストをオペレーターホワイトリストIDに関連付ける配列。

詳細 各オペレーターホワイトリストIDに対するオペレーターホワイトリストを格納します。 オペレーターホワイトリストはEnumerableSet.AddressSetとして実装されており、許可されたオペレーターのアドレスを保持します。


permittedContractReceiverAllowlists

javascript
mapping (uint120 => EnumerableSet.AddressSet) private permittedContractReceiverAllowlists;

概要 許可されたコントラクトレシーバーのホワイトリストをホワイトリストIDに関連付ける配列。

詳細 各許可されたコントラクトレシーバーのホワイトリストIDに対するホワイトリストを格納します。 ホワイトリストはEnumerableSet.AddressSetとして実装されており、許可されたコントラクトレシーバーのアドレスを保持します。


constructor

javascript
constructor(address defaultOwner) EOARegistry() {
    transferSecurityPolicies[TransferSecurityLevels.Zero] = TransferSecurityPolicy({
        callerConstraints: CallerConstraints.None,
        receiverConstraints: ReceiverConstraints.None
    });

    transferSecurityPolicies[TransferSecurityLevels.One] = TransferSecurityPolicy({
        callerConstraints: CallerConstraints.OperatorWhitelistEnableOTC,
        receiverConstraints: ReceiverConstraints.None
    });

    transferSecurityPolicies[TransferSecurityLevels.Two] = TransferSecurityPolicy({
        callerConstraints: CallerConstraints.OperatorWhitelistDisableOTC,
        receiverConstraints: ReceiverConstraints.None
    });

    transferSecurityPolicies[TransferSecurityLevels.Three] = TransferSecurityPolicy({
        callerConstraints: CallerConstraints.OperatorWhitelistEnableOTC,
        receiverConstraints: ReceiverConstraints.NoCode
    });

    transferSecurityPolicies[TransferSecurityLevels.Four] = TransferSecurityPolicy({
        callerConstraints: CallerConstraints.OperatorWhitelistEnableOTC,
        receiverConstraints: ReceiverConstraints.EOA
    });

    transferSecurityPolicies[TransferSecurityLevels.Five] = TransferSecurityPolicy({
        callerConstraints: CallerConstraints.OperatorWhitelistDisableOTC,
        receiverConstraints: ReceiverConstraints.NoCode
    });

    transferSecurityPolicies[TransferSecurityLevels.Six] = TransferSecurityPolicy({
        callerConstraints: CallerConstraints.OperatorWhitelistDisableOTC,
        receiverConstraints: ReceiverConstraints.EOA
    });

    uint120 id = ++lastOperatorWhitelistId;

    operatorWhitelistOwners[id] = defaultOwner;

    emit CreatedAllowlist(AllowlistTypes.Operators, id, "DEFAULT OPERATOR WHITELIST");
    emit ReassignedAllowlistOwnership(AllowlistTypes.Operators, id, defaultOwner);
}

概要 デフォルトのオーナーアドレスを受け取り、初期化を行います。

詳細 コントラクトを初期化し、トークンの転送セキュリティポリシーとオペレーターホワイトリストを設定します。 デフォルトのオーナーが指定され、各セキュリティポリシーの設定と初期イベントの発行が行われます。

引数

  • defaultOwner
    • デフォルトのオーナーアドレス。

applyCollectionTransferPolicy

javascript
function applyCollectionTransferPolicy(address caller, address from, address to) external view override {
    address collection = _msgSender();
    CollectionSecurityPolicy memory collectionSecurityPolicy = collectionSecurityPolicies[collection];
    TransferSecurityPolicy memory transferSecurityPolicy = 
        transferSecurityPolicies[collectionSecurityPolicy.transferSecurityLevel];
    
    if (transferSecurityPolicy.receiverConstraints == ReceiverConstraints.NoCode) {
        if (to.code.length > 0) {
            if (!isContractReceiverPermitted(collectionSecurityPolicy.permittedContractReceiversId, to)) {
                revert CreatorTokenTransferValidator__ReceiverMustNotHaveDeployedCode();
            }
        }
    } else if (transferSecurityPolicy.receiverConstraints == ReceiverConstraints.EOA) {
        if (!isVerifiedEOA(to)) {
            if (!isContractReceiverPermitted(collectionSecurityPolicy.permittedContractReceiversId, to)) {
                revert CreatorTokenTransferValidator__ReceiverProofOfEOASignatureUnverified();
            }
        }
    }

    if (transferSecurityPolicy.callerConstraints != CallerConstraints.None) {
        if(operatorWhitelists[collectionSecurityPolicy.operatorWhitelistId].length() > 0) {
            if (!isOperatorWhitelisted(collectionSecurityPolicy.operatorWhitelistId, caller)) {
                if (transferSecurityPolicy.callerConstraints == CallerConstraints.OperatorWhitelistEnableOTC) {
                    if (caller != from) {
                        revert CreatorTokenTransferValidator__CallerMustBeWhitelistedOperator();
                    }
                } else {
                    revert CreatorTokenTransferValidator__CallerMustBeWhitelistedOperator();
                }
            }
        }
    }
}

概要 コレクションのトークン転送ポリシーを適用する関数。

詳細 指定されたコレクションにおけるトークン転送操作に対してセキュリティポリシーを適用します。 転送が許可されるか拒否されるかは、セキュリティポリシーに基づいて判断されます。 特定の制約条件(CallerConstraints、ReceiverConstraints)が満たされているかどうかを確認し、許可されていない場合はrevertします。

引数

  • caller
    • トークン転送を呼び出すアドレス。
  • from
    • トークンの送信元アドレス。
  • to
    • トークンの受信先アドレス。

createOperatorWhitelist

javascript
function createOperatorWhitelist(string calldata name) external override returns (uint120) {
    uint120 id = ++lastOperatorWhitelistId;

    operatorWhitelistOwners[id] = _msgSender();

    emit CreatedAllowlist(AllowlistTypes.Operators, id, name);
    emit ReassignedAllowlistOwnership(AllowlistTypes.Operators, id, _msgSender());

    return id;
}

概要 新しいオペレーターホワイトリストを作成する関数。

詳細 この関数は新しいオペレーターホワイトリストを作成し、指定された名前で命名します。 作成されたホワイトリストは、呼び出し元のアドレスに所有権が委譲されます。 作成と所有者の設定が行われ、関連するイベントが発行されます。

引数

  • name
    • 新しいオペレーターホワイトリストの名前。

戻り値

  • id
    • 新しいオペレーターホワイトリストのID。

createPermittedContractReceiverAllowlist

javascript
function createPermittedContractReceiverAllowlist(string calldata name) external override returns (uint120) {
    uint120 id = ++lastPermittedContractReceiverAllowlistId;

    permittedContractReceiverAllowlistOwners[id] = _msgSender();

    emit CreatedAllowlist(AllowlistTypes.PermittedContractReceivers, id, name);
    emit ReassignedAllowlistOwnership(AllowlistTypes.PermittedContractReceivers, id, _msgSender());

    return id;
}

概要 新しい許可されたコントラクトレシーバーのホワイトリストを作成する関数。

詳細 この関数は新しい許可されたコントラクトレシーバーのホワイトリストを作成し、指定された名前で命名します。 作成されたホワイトリストは、呼び出し元のアドレスに所有権が委譲されます。 作成と所有者の設定が行われ、関連するイベントが発行されます。

引数

  • name
    • 新しい許可されたコントラクトレシーバーのホワイトリストの名前。

戻り値

  • id
    • 新しい許可されたコントラクトレシーバーのホワイトリストのID。

reassignOwnershipOfOperatorWhitelist

javascript
function reassignOwnershipOfOperatorWhitelist(uint120 id, address newOwner) external override {
    if(newOwner == address(0)) {
        revert CreatorTokenTransferValidator__AllowlistOwnershipCannotBeTransferredToZeroAddress();
    }

    _reassignOwnershipOfOperatorWhitelist(id, newOwner);
}

概要 オペレーターホワイトリストの所有権を新しい所有者に譲渡する関数。

詳細 この関数は、指定されたオペレーターホワイトリストの所有権を新しい所有者に譲渡します。 新しい所有者がゼロアドレスである場合や、呼び出し元が指定されたオペレーターホワイトリストの所有者でない場合にはエラーが発生します。 所有権が正常に譲渡され、関連するイベントが発行されます。

引数

  • id
    • オペレーターホワイトリストのID。
  • newOwner
    • 新しい所有者のアドレス。

reassignOwnershipOfPermittedContractReceiverAllowlist

javascript
function reassignOwnershipOfPermittedContractReceiverAllowlist(uint120 id, address newOwner) external override {
    if(newOwner == address(0)) {
        revert CreatorTokenTransferValidator__AllowlistOwnershipCannotBeTransferredToZeroAddress();
    }

    _reassignOwnershipOfPermittedContractReceiverAllowlist(id, newOwner);
}

概要 許可されたコントラクトレシーバーのホワイトリストの所有権を新しい所有者に譲渡する関数。

詳細 この関数は、指定された許可されたコントラクトレシーバーのホワイトリストの所有権を新しい所有者に譲渡します。 新しい所有者がゼロアドレスである場合や、呼び出し元が指定された許可されたコントラクトレシーバーのホワイトリストの所有者でない場合にはエラーが発生します。 所有権が正常に譲渡され、関連するイベントが発行されます。

引数

  • id
    • 許可されたコントラクトレシーバーのホワイトリストのID。
  • newOwner
    • 新しい所有者のアドレス。

renounceOwnershipOfOperatorWhitelist

javascript
function renounceOwnershipOfOperatorWhitelist(uint120 id) external override {
    _reassignOwnershipOfOperatorWhitelist(id, address(0));
}

概要 オペレーターホワイトリストの所有権を放棄し、ホワイトリストを変更できないようにする関数。

詳細 この関数は指定されたオペレーターホワイトリストの所有権を放棄し、ホワイトリストの変更を不可能にします。 呼び出し元が指定されたオペレーターホワイトリストの所有者でない場合にはエラーが発生します。 所有権が正常に放棄され、関連するイベントが発行されます。

引数

  • id
    • オペレーターホワイトリストのID。

renounceOwnershipOfPermittedContractReceiverAllowlist

javascript
function renounceOwnershipOfPermittedContractReceiverAllowlist(uint120 id) external override {
    _reassignOwnershipOfPermittedContractReceiverAllowlist(id, address(0));
}

概要 許可されたコントラクトレシーバーのホワイトリストの所有権を放棄し、ホワイトリストを変更できないようにする関数。

詳細 この関数は指定された許可されたコントラクトレシーバーのホワイトリストの所有権を放棄し、ホワイトリストの変更を不可能にします。 呼び出し元が指定された許可されたコントラクトレシーバーのホワイトリストの所有者でない場合にはエラーが発生します。 所有権が正常に放棄され、関連するイベントが発行されます。

引数

  • id
    • 許可されたコントラクトレシーバーのホワイトリストのID。

setTransferSecurityLevelOfCollection

javascript
function setTransferSecurityLevelOfCollection(
    address collection, 
    TransferSecurityLevels level) external override {
    _requireCallerIsNFTOrContractOwnerOrAdmin(collection);
    collectionSecurityPolicies[collection].transferSecurityLevel = level;
    emit SetTransferSecurityLevel(collection, level);
}

概要 コレクションの転送セキュリティレベルを設定するための関数。

詳細 この関数は、指定されたコレクションの転送セキュリティレベルを新しい値に設定します。 呼び出し元がコレクションコントラクト、指定されたコレクションの所有者または管理者でない場合にはエラーが発生します。 設定が正常に行われ、関連するイベントが発行されます。

引数

  • collection
    • コレクションのアドレス。
  • level
    • 適用する新しい転送セキュリティレベル。

setOperatorWhitelistOfCollection

javascript
function setOperatorWhitelistOfCollection(address collection, uint120 id) external override {
    _requireCallerIsNFTOrContractOwnerOrAdmin(collection);

    if (id > lastOperatorWhitelistId) {
        revert CreatorTokenTransferValidator__AllowlistDoesNotExist();
    }

    collectionSecurityPolicies[collection].operatorWhitelistId = id;
    emit SetAllowlist(AllowlistTypes.Operators, collection, id);
}

概要 コレクションのオペレーターホワイトリストを設定する関数。

詳細 この関数は、指定されたコレクションのオペレーターホワイトリストを新しい値に設定します。 呼び出し元がコレクションコントラクト、指定されたコレクションの所有者または管理者でない場合にはエラーが発生します。 また、指定されたオペレーターホワイトリストIDが存在しない場合にもエラーが発生します。 設定が正常に行われ、関連するイベントが発行されます。

引数

  • collection
    • コレクションのアドレス。
  • id
    • オペレーターホワイトリストのID。

setPermittedContractReceiverAllowlistOfCollection

javascript
function setPermittedContractReceiverAllowlistOfCollection(address collection, uint120 id) external override {
    _requireCallerIsNFTOrContractOwnerOrAdmin(collection);

    if (id > lastPermittedContractReceiverAllowlistId) {
        revert CreatorTokenTransferValidator__AllowlistDoesNotExist();
    }

    collectionSecurityPolicies[collection].permittedContractReceiversId = id;
    emit SetAllowlist(AllowlistTypes.PermittedContractReceivers, collection, id);
}

概要 コレクションの許可されたコントラクトレシーバーのホワイトリストを設定する関数。

詳細 この関数は、指定されたコレクションの許可されたコントラクトレシーバーのホワイトリストを新しい値に設定します。 呼び出し元がコレクションコントラクト、指定されたコレクションの所有者または管理者でない場合にはエラーが発生します。 また、指定された許可されたコントラクトレシーバーのホワイトリストIDが存在しない場合にもエラーが発生します。設定が正常に行われ、関連するイベントが発行されます。

引数

  • collection
    • コレクションのアドレス。
  • id
    • 許可されたコントラクトレシーバーのホワイトリストのID。

addOperatorToWhitelist

javascript
function addOperatorToWhitelist(uint120 id, address operator) external override {
    _requireCallerOwnsOperatorWhitelist(id);

    if (!operatorWhitelists[id].add(operator)) {
        revert CreatorTokenTransferValidator__AddressAlreadyAllowed();
    }

    emit AddedToAllowlist(AllowlistTypes.Operators, id, operator);
}

概要 オペレーターホワイトリストにオペレーターを追加するための関数。

詳細 この関数は、指定されたオペレーターホワイトリストに指定されたオペレーターを追加します。 呼び出し元が指定されたオペレーターホワイトリストの所有者でない場合や、オペレーターが既に許可されている場合にはエラーが発生します。 オペレーターが正常に追加され、関連するイベントが発行されます。

引数

  • id
    • オペレーターホワイトリストのID。
  • operator
    • 追加するオペレーターのアドレス。

addPermittedContractReceiverToAllowlist

javascript
function addPermittedContractReceiverToAllowlist(uint120 id, address receiver) external override {
    _requireCallerOwnsPermittedContractReceiverAllowlist(id);

    if (!permittedContractReceiverAllowlists[id].add(receiver)) {
        revert CreatorTokenTransferValidator__AddressAlreadyAllowed();
    }

    emit AddedToAllowlist(AllowlistTypes.PermittedContractReceivers, id, receiver);
}

概要 許可されたコントラクトレシーバーのホワイトリストにコントラクトアドレスを追加する関数。

詳細 この関数は、指定された許可されたコントラクトレシーバーのホワイトリストに指定されたコントラクトアドレスを追加します。 呼び出し元が指定された許可されたコントラクトレシーバーのホワイトリストの所有者でない場合や、コントラクトアドレスが既に許可されている場合にはエラーが発生します。 コントラクトアドレスが正常に追加され、関連するイベントが発行されます。

引数

  • id
    • 許可されたコントラクトレシーバーのホワイトリストのID。
  • receiver
    • 追加するコントラクトのアドレス。

removeOperatorFromWhitelist

javascript
function removeOperatorFromWhitelist(uint120 id, address operator) external override {
    _requireCallerOwnsOperatorWhitelist(id);

    if (!operatorWhitelists[id].remove(operator)) {
        revert CreatorTokenTransferValidator__AddressNotAllowed();
    }

    emit RemovedFromAllowlist(AllowlistTypes.Operators, id, operator);
}

概要 オペレーターホワイトリストからオペレーターを削除する関数。

詳細 この関数は、指定されたオペレーターホワイトリストから指定されたオペレーターを削除します。 呼び出し元が指定されたオペレーターホワイトリストの所有者でない場合や、オペレーターが指定されたオペレーターホワイトリストに含まれていない場合にはエラーが発生します。 オペレーターが正常に削除され、関連するイベントが発行されます。

引数

  • id
    • オペレーターホワイトリストのID。
  • operator
    • 削除するオペレーターのアドレス。

removePermittedContractReceiverFromAllowlist

javascript
function removePermittedContractReceiverFromAllowlist(uint120 id, address receiver) external override {
    _requireCallerOwnsPermittedContractReceiverAllowlist(id);

    if (!permittedContractReceiverAllowlists[id].remove(receiver)) {
        revert CreatorTokenTransferValidator__AddressNotAllowed();
    }

    emit RemovedFromAllowlist(AllowlistTypes.PermittedContractReceivers, id, receiver);
}

概要 許可されたコントラクトレシーバーのホワイトリストからコントラクトアドレスを削除する関数。

詳細 この関数は、指定された許可されたコントラクトレシーバーのホワイトリストから指定されたコントラクトアドレスを削除します。 呼び出し元が指定された許可されたコントラクトレシーバーのホワイトリストの所有者でない場合や、コントラクトアドレスが指定された許可されたコントラクトレシーバーのホワイトリストに含まれていない場合にはエラーが発生します。 コントラクトアドレスが正常に削除され、関連するイベントが発行されます。

引数

  • id
    • 許可されたコントラクトレシーバーのホワイトリストのID。
  • receiver
    • 削除するコントラクトのアドレス。

getCollectionSecurityPolicy

javascript
function getCollectionSecurityPolicy(address collection) 
    external view override returns (CollectionSecurityPolicy memory) {
    return collectionSecurityPolicies[collection];
}

概要 指定されたコレクションのセキュリティポリシーを取得する関数。

詳細 この関数は、指定されたコレクションのセキュリティポリシーを取得します。 セキュリティポリシーには転送セキュリティレベル、オペレーターホワイトリストID、許可されたコントラクトレシーバーのホワイトリストIDが含まれています。

引数

  • collection
    • コレクションのアドレス。

戻り値

  • CollectionSecurityPolicy
    • コレクションのセキュリティポリシー情報を含む構造体。

getWhitelistedOperators

javascript
function getWhitelistedOperators(uint120 id) external view override returns (address[] memory) {
    return operatorWhitelists[id].values();
}

概要 オペレーターホワイトリスト内のホワイトリストされたオペレーターのリストを取得する関数。

詳細 この関数は、指定されたオペレーターホワイトリスト内のホワイトリストされたオペレーターのアドレスのリストを取得します。

引数

  • id
    • オペレーターホワイトリストのID。

戻り値

  • address[]
    • ホワイトリストされたオペレーターのアドレスのリスト。

getPermittedContractReceivers

javascript
function getPermittedContractReceivers(uint120 id) external view override returns (address[] memory) {
    return permittedContractReceiverAllowlists[id].values();
}

概要 指定された許可されたコントラクトレシーバーのホワイトリスト内の許可されたコントラクトレシーバーのアドレスのリストを取得する関数。

詳細 この関数は、指定された許可されたコントラクトレシーバーのホワイトリスト内のすべての許可されたコントラクトレシーバーのアドレスのリストを取得します。

引数

  • id
    • 許可されたコントラクトレシーバーのホワイトリストのID。

戻り値

  • address[]
    • ホワイトリスト内の許可されたコントラクトレシーバーのアドレスのリスト。

isOperatorWhitelisted

javascript
function isOperatorWhitelisted(uint120 id, address operator) public view override returns (bool) {
    return operatorWhitelists[id].contains(operator);
}

概要 指定されたオペレーターホワイトリスト内に特定のオペレーターが含まれているか確認する関数。

詳細 この関数は、指定されたオペレーターホワイトリスト内に特定のオペレーターが含まれているかどうかを確認します。含まれている場合はTrueを、含まれていない場合はFalseを返します。

引数

  • id
    • オペレーターホワイトリストのID。
  • operator
    • チェックするオペレーターのアドレス。

戻り値

  • bool
    • オペレーターが指定されたオペレーターホワイトリスト内に含まれている場合はTrue、それ以外の場合はFalse。

isContractReceiverPermitted

javascript
function isContractReceiverPermitted(uint120 id, address receiver) public view override returns (bool) {
    return permittedContractReceiverAllowlists[id].contains(receiver);
}

概要 指定された許可されたコントラクトレシーバーのホワイトリスト内に特定のコントラクトアドレスが含まれているか確認する関数。

詳細 この関数は、指定された許可されたコントラクトレシーバーのホワイトリスト内に特定のコントラクトアドレスが含まれているかどうかを確認します。 含まれている場合はTrueを、含まれていない場合はFalseを返します。

引数

  • id
    • 許可されたコントラクトレシーバーのホワイトリストのID。
  • receiver
    • チェックするコントラクトのアドレス。

戻り値

  • bool
    • コントラクトアドレスが指定された許可されたコントラクトレシーバーのホワイトリスト内に含まれている場合はTrue、それ以外の場合はFalse。

supportsInterface

javascript
function supportsInterface(bytes4 interfaceId) public view virtual override(EOARegistry, IERC165) returns (bool) {
    return
        interfaceId == type(ITransferValidator).interfaceId ||
        interfaceId == type(ITransferSecurityRegistry).interfaceId ||
        interfaceId == type(ICreatorTokenTransferValidator).interfaceId ||
        super.supportsInterface(interfaceId);
}

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

詳細 この関数は、指定されたインターフェースがサポートされているかどうかを確認します。 サポートされている場合はTrueを、サポートされていない場合はFalseを返します。 この関数はERC165インターフェースをサポートし、トランスファーバリデータ関連のインターフェースを確認します。

引数

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

戻り値

  • bool
    • 指定されたインターフェースがサポートされている場合はTrue、それ以外の場合はFalse。

_requireCallerIsNFTOrContractOwnerOrAdmin

javascript
function _requireCallerIsNFTOrContractOwnerOrAdmin(address tokenAddress) internal view {
    bool callerHasPermissions = false;
    if(tokenAddress.code.length > 0) {
        callerHasPermissions = _msgSender() == tokenAddress;
        if(!callerHasPermissions) {

            try IOwnable(tokenAddress).owner() returns (address contractOwner) {
                callerHasPermissions = _msgSender() == contractOwner;
            } catch {}

            if(!callerHasPermissions) {
                try IAccessControl(tokenAddress).hasRole(DEFAULT_ACCESS_CONTROL_ADMIN_ROLE, _msgSender()) 
                    returns (bool callerIsContractAdmin) {
                    callerHasPermissions = callerIsContractAdmin;
                } catch {}
            }
        }
    }

    if(!callerHasPermissions) {
        revert CreatorTokenTransferValidator__CallerMustHaveElevatedPermissionsForSpecifiedNFT();
    }
}

概要 呼び出し元がNFTまたはコントラクトの所有者または管理者であることを確認する関数。

詳細 この関数は、呼び出し元が指定されたNFTまたはコントラクトの所有者または管理者であるかどうかを確認します 呼び出し元がこれらの権限を持っていない場合、エラーが発生します。

引数

  • tokenAddress
    • 確認するNFTまたはコントラクトのアドレス。

_reassignOwnershipOfOperatorWhitelist

javascript
function _reassignOwnershipOfOperatorWhitelist(uint120 id, address newOwner) private {
    _requireCallerOwnsOperatorWhitelist(id);
    operatorWhitelistOwners[id] = newOwner;
    emit ReassignedAllowlistOwnership(AllowlistTypes.Operators, id, newOwner);
}

概要 オペレーターホワイトリストの所有権を別の所有者に移転する関数。

詳細 この関数は、指定されたオペレーターホワイトリストの所有権を新しい所有者に移転します。 呼び出し元が指定されたオペレーターホワイトリストの所有者でない場合、エラーが発生します。 所有権が正常に移転され、関連するイベントが発行されます。

引数

  • id
    • オペレーターホワイトリストのID。
  • newOwner
    • 新しい所有者のアドレス。

_reassignOwnershipOfPermittedContractReceiverAllowlist

javascript
function _reassignOwnershipOfPermittedContractReceiverAllowlist(uint120 id, address newOwner) private {
    _requireCallerOwnsPermittedContractReceiverAllowlist(id);
    permittedContractReceiverAllowlistOwners[id] = newOwner;
    emit ReassignedAllowlistOwnership(AllowlistTypes.PermittedContractReceivers, id, newOwner);
}

概要 許可されたコントラクトレシーバーのホワイトリストの所有権を別の所有者に移転する関数。

詳細 この関数は、指定された許可されたコントラクトレシーバーのホワイトリストの所有権を新しい所有者に移転します。 呼び出し元が指定された許可されたコントラクトレシーバーのホワイトリストの所有者でない場合、エラーが発生します。 所有権が正常に移転され、関連するイベントが発行されます。

引数

  • id
    • 許可されたコントラクトレシーバーのホワイトリストのID。
  • newOwner
    • 新しい所有者のアドレス。

_requireCallerOwnsOperatorWhitelist

javascript
function _requireCallerOwnsOperatorWhitelist(uint120 id) private view {
    if (_msgSender() != operatorWhitelistOwners[id]) {
        revert CreatorTokenTransferValidator__CallerDoesNotOwnAllowlist();
    }
}

概要 呼び出し元が指定されたオペレーターホワイトリストの所有者であることを確認する関数。

詳細 この関数は、呼び出し元が指定されたオペレーターホワイトリストの所有者であるかどうかを確認します。 所有者でない場合、エラーが発生します。

引数

  • id
    • オペレーターホワイトリストのID。

_requireCallerOwnsPermittedContractReceiverAllowlist

javascript
function _requireCallerOwnsPermittedContractReceiverAllowlist(uint120 id) private view {
    if (_msgSender() != permittedContractReceiverAllowlistOwners[id]) {
        revert CreatorTokenTransferValidator__CallerDoesNotOwnAllowlist();
    }
}

概要 呼び出し元が指定された許可されたコントラクトレシーバーのホワイトリストの所有者であることを確認する関数。

詳細 この関数は、呼び出し元が指定された許可されたコントラクトレシーバーのホワイトリストの所有者であるかどうかを確認します。 所有者でない場合、エラーが発生します。

引数

  • id
    • 許可されたコントラクトレシーバーのホワイトリストのID。

参考

最後に

今回は「オンチェーンロイヤリティを強制化する仕組みを提案しているERC721C」についてまとめてきました! いかがだったでしょうか?

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

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