Skip to content

Instantly share code, notes, and snippets.

@dievardump
Last active February 4, 2023 09:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dievardump/eb3ed9e226755d4e1fc96d6df78dd71a to your computer and use it in GitHub Desktop.
Save dievardump/eb3ed9e226755d4e1fc96d6df78dd71a to your computer and use it in GitHub Desktop.
Simple Operator Filter for ERC721 & ERC721A

What

This is a quick implementation of the OpenSea OperatorFilter with a way to activate / deactivate it over time

Usage

ERC721A

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import {WithOperatorFilter} from './WithOperatorFilter.sol';
import {ERC721A} from "erc721a/contracts/ERC721A.sol";
import {ERC2981} from '@openzeppelin/contracts/token/common/ERC2981.sol';

contract MyToken is ERC721A, WithOperatorFilter, Ownable {

    constructor() ERC721A('Token', 'TOKEN') {}
    
    /// @inheritdoc ERC721A
    function supportsInterface(bytes4 interfaceId)
        public
        view
        virtual
        override(ERC721A, ERC2981)
        returns (bool)
    {
        return ERC721A.supportsInterface(interfaceId) || ERC2981.supportsInterface(interfaceId);
    }
    
    /// @inheritdoc ERC2981
    function royaltyInfo(uint256 tokenId, uint256 amount) public view override returns (address recipient, uint256 amount) {
      return (owner(), amount * 1000 / 10000);
    }

    /////////////////////////////////////////////////////////
    // Interactions                                        //
    /////////////////////////////////////////////////////////

    /// @inheritdoc ERC721A
    /// @dev overrode to add the FilterOperator
    function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) public payable override onlyAllowedOperator(from) {
        super.transferFrom(from, to, tokenId);
    }

    /// @inheritdoc ERC721A
    /// @dev overrode to add the FilterOperator
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId
    ) public payable override onlyAllowedOperator(from) {
        super.safeTransferFrom(from, to, tokenId);
    }

    /// @inheritdoc ERC721A
    /// @dev overrode to add the FilterOperator
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        bytes memory data
    ) public payable override onlyAllowedOperator(from) {
        super.safeTransferFrom(from, to, tokenId, data);
    }

    /// @inheritdoc ERC721A
    /// @dev overrode to add the FilterOperator
    function setApprovalForAll(address operator, bool _approved)
        public
        override
        onlyAllowedOperatorForApproval(operator, _approved)
    {
        super.setApprovalForAll(operator, _approved);
    }
    
    
    /////////////////////////////////////////////////////////
    // Gated Owner                                         //
    /////////////////////////////////////////////////////////
    
    /// @notice Allows owner to switch on/off the OperatorFilter
    /// @param newIsEnabled the new state
    function setIsOperatorFilterEnabled(bool newIsEnabled) public onlyOwner {
        isOperatorFilterEnabled = newIsEnabled;
    }
}

ERC721

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import {WithOperatorFilter} from './WithOperatorFilter.sol';
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import {ERC2981} from '@openzeppelin/contracts/token/common/ERC2981.sol';

contract MyToken is ERC721, WithOperatorFilter, Ownable {

    constructor() ERC721('Token', 'TOKEN') {}
    
    /// @inheritdoc ERC721
    function supportsInterface(bytes4 interfaceId)
        public
        view
        virtual
        override(ERC721, ERC2981)
        returns (bool)
    {
        return ERC721.supportsInterface(interfaceId) || ERC2981.supportsInterface(interfaceId);
    }
    
    /// @inheritdoc ERC2981
    function royaltyInfo(uint256 tokenId, uint256 amount) public view override returns (address, uint256) {
      return (owner(), amount * 1000 / 10000);
    }
    
    /////////////////////////////////////////////////////////
    // Interactions                                        //
    /////////////////////////////////////////////////////////

    /// @inheritdoc ERC721
    /// @dev overrode to add the FilterOperator
    function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) public override onlyAllowedOperator(from) {
        super.transferFrom(from, to, tokenId);
    }

    /// @inheritdoc ERC721
    /// @dev overrode to add the FilterOperator
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId
    ) public override onlyAllowedOperator(from) {
        super.safeTransferFrom(from, to, tokenId);
    }

    /// @inheritdoc ERC721
    /// @dev overrode to add the FilterOperator
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        bytes memory data
    ) public override onlyAllowedOperator(from) {
        super.safeTransferFrom(from, to, tokenId, data);
    }

    /// @inheritdoc ERC721
    /// @dev overrode to add the FilterOperator
    function setApprovalForAll(address operator, bool _approved)
        public
        override
        onlyAllowedOperatorForApproval(operator, _approved)
    {
        super.setApprovalForAll(operator, _approved);
    }
    
    
    /////////////////////////////////////////////////////////
    // Gated Owner                                         //
    /////////////////////////////////////////////////////////
    
    /// @notice Allows owner to switch on/off the OperatorFilter
    /// @param newIsEnabled the new state
    function setIsOperatorFilterEnabled(bool newIsEnabled) public onlyOwner {
        isOperatorFilterEnabled = newIsEnabled;
    }
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
/// @title WithOperatorFilter
/// @author dev by @dievardump
/// @notice Adds OpenSea's OperatorFilter registry management
abstract contract WithOperatorFilter {
error OperatorNotAllowed(address operator);
address constant DEFAULT_SUBSCRIPTION = address(0x3cc6CddA760b79bAfa08dF41ECFA224f810dCeB6);
IOperatorFilterRegistry public constant OPERATOR_FILTER_REGISTRY =
IOperatorFilterRegistry(0x000000000000AAeB6D7670E522A718067333cd4E);
bool public isOperatorFilterEnabled = true;
constructor() {
// auto subscribe to the default subscription
if (address(OPERATOR_FILTER_REGISTRY).code.length > 0) {
OPERATOR_FILTER_REGISTRY.registerAndSubscribe(address(this), DEFAULT_SUBSCRIPTION);
}
}
/////////////////////////////////////////////////////////
// modifier //
/////////////////////////////////////////////////////////
/// @notice this modifier checks if "msg.sender" is not denied to do transfers
/// @param from the user we want to transfer the nft from
modifier onlyAllowedOperator(address from) virtual {
// if from == msg.sender, always allow
if (from != msg.sender) {
// reverts if not allowed
_checkOperatorFilter(msg.sender);
}
_;
}
/// @notice this modifier checks if "operator" is not denied to do transfers on the current contract
/// @dev the check will only be performed when the user is trying to approve the `operator` not unapprove
/// @param operator the operator to check
/// @param _approved if we are currently trying to approve the operator
modifier onlyAllowedOperatorForApproval(address operator, bool _approved) virtual {
// only check when the user tries to approve an operator, unapprove should always work
if (_approved) {
// reverts if not allowed
_checkOperatorFilter(operator);
}
_;
}
/////////////////////////////////////////////////////////
// Internals //
/////////////////////////////////////////////////////////
/// @dev Internally checks if the operator filter is enabled and if the current operator is allowed to transfer
/// @param operator the owner of the item
function _checkOperatorFilter(address operator) internal view {
// if the operator filter is on, and the operator registry is
if (isOperatorFilterEnabled && address(OPERATOR_FILTER_REGISTRY).code.length > 0) {
// if the operator is not allowed, revert
if (!OPERATOR_FILTER_REGISTRY.isOperatorAllowed(address(this), operator)) {
revert OperatorNotAllowed(operator);
}
}
}
}
interface IOperatorFilterRegistry {
function isOperatorAllowed(address registrant, address operator) external view returns (bool);
function register(address registrant) external;
function registerAndSubscribe(address registrant, address subscription) external;
function registerAndCopyEntries(address registrant, address registrantToCopy) external;
function updateOperator(address registrant, address operator, bool filtered) external;
function updateOperators(address registrant, address[] calldata operators, bool filtered) external;
function updateCodeHash(address registrant, bytes32 codehash, bool filtered) external;
function updateCodeHashes(address registrant, bytes32[] calldata codeHashes, bool filtered) external;
function subscribe(address registrant, address registrantToSubscribe) external;
function unsubscribe(address registrant, bool copyExistingEntries) external;
function subscriptionOf(address addr) external returns (address registrant);
function subscribers(address registrant) external returns (address[] memory);
function subscriberAt(address registrant, uint256 index) external returns (address);
function copyEntriesOf(address registrant, address registrantToCopy) external;
function isOperatorFiltered(address registrant, address operator) external returns (bool);
function isCodeHashOfFiltered(address registrant, address operatorWithCode) external returns (bool);
function isCodeHashFiltered(address registrant, bytes32 codeHash) external returns (bool);
function filteredOperators(address addr) external returns (address[] memory);
function filteredCodeHashes(address addr) external returns (bytes32[] memory);
function filteredOperatorAt(address registrant, uint256 index) external returns (address);
function filteredCodeHashAt(address registrant, uint256 index) external returns (bytes32);
function isRegistered(address addr) external returns (bool);
function codeHashOf(address addr) external returns (bytes32);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment