Polygon Basic Integration and Meta-transactions

OpenSea recently began supporting Polygon NFTs on its platform. Follow this quick tutorial to make your Polygon (formerly Matic) contract enabled on OpenSea with gasless minting!

Polygon (formerly Matic) Blockchain

If you're developing on the Polygon L2 blockchain, you should make sure to do 2 key things to make minting and selling on OpenSea more straightforward:

  • Overriding isApprovedForAll() to extra signatures and gas fees for users
  • Enabling Meta-transactions to allow gas-less transactions

Overriding isApprovedForAll() to reduce trading friction

In your isApprovedForAll method, you should allow OpenSea's 0x marketplace proxy contract to operate on your NFT's contract. OpenSea has a different proxy contract address for ERC721 and ERC1155, so make sure you are using the correct one.

๐Ÿ“˜

Why am I doing this?

In order to allow OpenSea to transact on your assets (such as selling and transferring assets), OpenSea's smart contract must be allowed to operate on your smart contract. Typically, you (a seller) would have to call your smart contract's setApprovalForAll() method with OpenSea's address to approve it as an operator, which would cost you gas.

Overriding the isApprovedForAll() to automatically bypass the ERC721 / ERC1155's default implementation if the operator is OpenSea's proxy contract allows you to begin selling items with OpenSea without paying expensive gas fees.

This will reduce friction for you as a seller when you list items on OpenSea, and we think that's pretty awesome.

Code Example for ERC721

/**
   * Override isApprovedForAll to auto-approve OS's proxy contract
   */
    function isApprovedForAll(
        address _owner,
        address _operator
    ) public override view returns (bool isOperator) {
      // if OpenSea's ERC721 Proxy Address is detected, auto-return true
      // for Polygon's Mumbai testnet, use 0xff7Ca10aF37178BdD056628eF42fD7F799fAc77c
        if (_operator == address(0x58807baD0B376efc12F5AD86aAc70E78ed67deaE)) {
            return true;
        }
        
        // otherwise, use the default ERC721.isApprovedForAll()
        return ERC721.isApprovedForAll(_owner, _operator);
    }

Code Example for ERC1155

/**
   * Override isApprovedForAll to auto-approve OS's proxy contract
   */
    function isApprovedForAll(
        address _owner,
        address _operator
    ) public override view returns (bool isOperator) {
        // if OpenSea's ERC1155 Proxy Address is detected, auto-return true
            // for Polygon's Mumbai testnet, use 0x53d791f18155C211FF8b58671d0f7E9b50E596ad
       if (_operator == address(0x207Fa8Df3a17D96Ca7EA4f2893fcdCb78a304101)) {
            return true;
        }
        // otherwise, use the default ERC1155.isApprovedForAll()
        return ERC1155.isApprovedForAll(_owner, _operator);
    }

This will mean that the user doesn't have to pay gas the first time they list an item on OpenSea. This is particularly important on Polygon, where gas is paid in MATIC, which is still somewhat difficult for users to obtain.

๐Ÿšง

OpenZeppelin's contracts

For the the isApprovedForAll() examples above to work as expected, make sure you are using a version of OpenZeppelin's ERC721 / ERC1155 contracts version at least 4.x. Older version's of OpenZeppelin's contracts will not support this mechanism for allowing OpenSea's proxy contracts to transact on their behalf.

Ideally, you are always using the latest releases for all your dependencies when it comes to development!

Meta-transactions

Make sure to enable meta-transactions for your Polygon contract to allow for gass-less transactions. As mentioned above, most users still do not have Polygon's native MATIC token in their wallets to pay for gas fees. Your smart contracts should support meta-transactions so that OpenSea can abstract away gas payments for users for methods such as transfers and sales. In order to do this, you'll want to do the following:

  1. Inherit the latest ERC721 / ERC1155 standard from OpenZeppelin.
  2. Override your contract's _msgSender() method to use a ContentMixin custom implementation. Code example below (derived from our GitHub meta-transaction repo, linked below):
/**
 * https://github.com/maticnetwork/pos-portal/blob/master/contracts/common/ContextMixin.sol
 */
abstract contract ContextMixin {
    function msgSender()
        internal
        view
        returns (address payable sender)
    {
        if (msg.sender == address(this)) {
            bytes memory array = msg.data;
            uint256 index = msg.data.length;
            assembly {
                // Load the 32 bytes word from memory with the address on the lower 20 bytes, and mask those.
                sender := and(
                    mload(add(array, index)),
                    0xffffffffffffffffffffffffffffffffffffffff
                )
            }
        } else {
            sender = payable(msg.sender);
        }
        return sender;
    }
}



contract MyPolygonERC721NFT is ERC721, ContextMixin, ERC2771Context {

    /**
     * This is used instead of msg.sender as transactions won't be sent by the original token owner, but by OpenSea.
     */
    function _msgSender()
        internal
        override
        view
        returns (address sender)
    {
        return ContextMixin.msgSender();
    }
}

Sample implementations of the above can be found here:
https://github.com/ProjectOpenSea/meta-transactions/tree/main/contracts

Read more about gassless transactions and meta-transactions here:
https://docs.openzeppelin.com/learn/sending-gasless-transactions