🍉

OpenSeaでfee設定に必要なoperator-filter-registryを読んでみる

2022/11/15に公開

OpenSeaが11月10日に発表したツイートでは、

新しいコレクションに対してクリエイターフィーを認識するためには、オンチェーンでの執行ツールを採用する必要があります。
私たちの新しいレジストリはそのようなツールの1つですが、他にもありますし、私たちはそれらもサポートしています。

と記載されており、feeを受け取るにはロイヤリティを強制するコントラクトを使用しなければならないと書かれています。
今回は「このコントラクトでは何が強制されているのか」を読み解いていきます。

https://twitter.com/opensea/status/1590466349683576832

リンク集

🔗 実装例(Azukiエンジニア: cygaar)
https://github.com/cygaar/OpenSea-NFT-Template

🔗 operator-filter-registry
https://github.com/ProjectOpenSea/operator-filter-registry

実装例

  • ERC721
    transferFrom,safeTransferFrom(byteあり,なし)をoverrideして、onlyAllowedOperator(from)のmodifierを追加すれば良さそうです。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "operator-filter-registry/src/OperatorFilterer.sol";

contract SampleNFT721 is ERC721, Ownable, OperatorFilterer {
    constructor() ERC721("SampleNFT", "SAMPLE") OperatorFilterer(address(0), false) {}

    /* Add minting logic here */

    /* Add metadata logic here */

    function transferFrom(address from, address to, uint256 tokenId) public override onlyAllowedOperator(from) {
        super.transferFrom(from, to, tokenId);
    }

    function safeTransferFrom(address from, address to, uint256 tokenId) public override onlyAllowedOperator(from) {
        super.safeTransferFrom(from, to, tokenId);
    }

    function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data)
        public
        override
        onlyAllowedOperator(from)
    {
        super.safeTransferFrom(from, to, tokenId, data);
    }
}

onlyAllowedOperatorを読む

では、実際に何を行っているのかを確認していきます。
OperatorFilterer.sol

    modifier onlyAllowedOperator(address from) virtual {
        // Check registry code length to facilitate testing in environments without a deployed registry.
        if (address(operatorFilterRegistry).code.length > 0) {
            // Allow spending tokens from addresses with balance
            // Note that this still allows listings and marketplaces with escrow to transfer tokens if transferred
            // from an EOA.
	    
	    // 通常のTransfer(fromが自分)の場合は制限を受けない
            if (from == msg.sender) {
                _;
                return;
            }
	    
	    // msg.senderとfromの両方が許可されていればOK,そうでなければrevert
            if (
                !(
                    operatorFilterRegistry.isOperatorAllowed(address(this), msg.sender)
                        && operatorFilterRegistry.isOperatorAllowed(address(this), from)
                )
            ) {
                revert OperatorNotAllowed(msg.sender);
            }
        }
        _;
    }

ちなみにisOperatorAllowedを見てみると、制限されたコントラクト(ブラックリスト)はrevert、それ以外はTrueを返すようになっています。

※制限されたコントラクト(ブラックリスト)はこちら

OperatorFilterRegistry.sol
    /**
     * @notice Returns true if operator is not filtered for a given token, either by address or codeHash. Also returns
     *         true if supplied registrant address is not registered.
     */
    function isOperatorAllowed(address registrant, address operator) external view returns (bool) {
        address registration = _registrations[registrant];
        if (registration != address(0)) {
            EnumerableSet.AddressSet storage filteredOperatorsRef;
            EnumerableSet.Bytes32Set storage filteredCodeHashesRef;

            filteredOperatorsRef = _filteredOperators[registration];
            filteredCodeHashesRef = _filteredCodeHashes[registration];

            if (filteredOperatorsRef.contains(operator)) {
                revert AddressFiltered(operator);
            }
            if (operator.code.length > 0) {
                bytes32 codeHash = operator.codehash;
                if (filteredCodeHashesRef.contains(codeHash)) {
                    revert CodeHashFiltered(operator, codeHash);
                }
            }
        }
        return true;
    }

Discussion