Before users can sell their NFTs across marketplaces such as OpenSea or LooksRare, users must first approve the marketplace as an "operator" of their assets by calling a contracts setApprovalForAll function. This is a necessary security measure—part of the ERC721 standard—that prevents third parties from accessing others' assets.

These approvals can cost your users around $10 to $30 in gas fees for each collection, per marketplace. This is what it looks like to the end user:

openseaapproval

Before OpenSea or LooksRare attempts to list an item, they will call the contracts isApprovedForAll function to check if the marketplace (the operator) has already been approved to spend the users tokens. This simply returns true or false depending if the operator address is inside a mapping (set in the setApprovalForAll function).

To enable free listings, we can simply override the isApprovedForAll function to return true if the operator is either OpenSea or LooksRare. In practice this looks a little something like this:

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

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

contract OwnableDelegateProxy {}
contract ProxyRegistry { mapping(address => OwnableDelegateProxy) public proxies; }

abstract contract ERC721Preapproved is ERC721, Ownable {
    address private immutable _openSeaProxyRegistryAddress;
    address private immutable _looksRareTransferManagerAddress;
    bool private _isMarketplacesApproved = true;

    constructor (
        string memory name,
        string memory symbol,
        address openSeaProxyRegistryAddress,
        address looksRareTransferManagerAddress
    ) ERC721(name, symbol) {
        _openSeaProxyRegistryAddress = openSeaProxyRegistryAddress;
        _looksRareTransferManagerAddress = looksRareTransferManagerAddress;
    }

    function setMarketplacesApproved(bool isMarketplacesApproved) external onlyOwner {
        _isMarketplacesApproved = isMarketplacesApproved;
    }

    function isApprovedForAll(address owner, address operator) public view override returns (bool) {
        ProxyRegistry proxyRegistry = ProxyRegistry(_openSeaProxyRegistryAddress);
        if (_isMarketplacesApproved && (address(proxyRegistry.proxies(owner)) == operator || _looksRareTransferManagerAddress == operator))
            return true;

        return super.isApprovedForAll(owner, operator);
    }
}

An implementation looks like this:

contract MyNFTProject is ERC721Preapproved {
    constructor (
        string memory name,
        string memory symbol,
        address openSeaProxyRegistryAddress,
        address looksRareTransferManagerAddress
    ) ERC721Preapproved(name, symbol, openSeaProxyRegistryAddress, looksRareTransferManagerAddress) {

    }
}

Whilst deploying, simply pass the OpenSea and LooksRare contract addresses to your contracts constructor. For mainnet, they are 0xa5409ec958C83C3f309868babACA7c86DCB077c1 and 0xf42aa99F011A1fA7CDA90E5E98b277E306BcA83e respectively.

For security reasons it's important that the OpenSea and LooksRare contract addresses are immutable, i.e., unchangeable after deployment. If you were to add a function called updateMarketplaceContractAddress() then this creates an implicit trust issue where potentially the contracts owner could update these addresses to their own wallet, effectively owning all of the contracts NFTs.

If we assume 5,000 unique holders in a 10K collection and take the median gas cost of ~$15, then you will be saving your users approximately $75,000 in gas fees! 🔥 It also removes any friction from users selling their NFTs on the secondary market.

Questions? Looking for your perfect launch partner? Reach out to hello@topdogstudios.io.

The Top Dog Team ❤