EIP2771 implementation in NFT smart contracts

Hi, I wanted to create NFT smart contracts with meta transactions. We wanted to allow users to claim NFT for free where we will pay the gas fee for transferring it to their wallet address.

I've discovered that I need to use Openzeppelin ERC2771Context so I imported it into my contract. somehow it asked me to override functions _msgData and _msgSender.

I did that by using this snippet that i found

  function _msgSender() internal view override(Context, ERC2771Context)
      returns (address sender) {
      sender = ERC2771Context._msgSender();
  }

  function _msgData() internal view override(Context, ERC2771Context)
      returns (bytes calldata) {
      return ERC2771Context._msgData();
}

but weirdly enough i received errors saying

Function needs to specify overridden contract "Context".
and
Invalid contract specified in override list: "Context".

I have no idea why it doesn't found Context even though I have imported the ERC2771Context and also installed the extension in my node module. What do i need to do from here?

for a better overview here is the whole code:

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

import "@thirdweb-dev/contracts/base/ERC721Base.sol";
import "@openzeppelin/contracts/metatx/ERC2771Context.sol";

contract RiotPass is  ERC2771Context, ERC721Base {

    address public metadataContract;

      constructor(
        string memory _name,
        string memory _symbol,
        address _royaltyRecipient,
        uint128 _royaltyBps,
        address _trustedForwarder
    )
        ERC721Base(
            _name,
            _symbol,
            _royaltyRecipient,
            _royaltyBps
        )ERC2771Context(address(_trustedForwarder))
    {
        //trustedForwarder = _trustedForwarder;
        //_setTrustedForwarder(forwarder_);
    }
    
    //string public versionRecipient = "2.2.0";

  function _msgSender() internal view override(Context, ERC2771Context)
      returns (address sender) {
      sender = ERC2771Context._msgSender();
  }

  function _msgData() internal view override(Context, ERC2771Context)
      returns (bytes calldata) {
      return ERC2771Context._msgData();
}

    event claimed(address claimant, uint256 amount);

    function setTrustedForwarder(address _trustedForwarder) external onlyOwner {

    }

    /**
    * @dev override the tokenURI function 
     */
    function tokenURI(uint256 tokenId)  public view virtual override returns (string memory) {
        require (tokenId <= _totalMinted(), "Token ID does not exist");
        return ImetadataContract(metadataContract).fetchMetadata(tokenId);
    }

    /**
    * @dev setting function to call onchain metadata
     */
    function setMetadataAddress(address _address) external onlyOwner {
        metadataContract = _address;
    }

    /**
    * @dev we set claim for our users to get NFT
    */
    function claim(uint256 _amount) public {
        //require(_amount > 0 && _amount < 6);
        _safeMint(msg.sender, _amount);

        emit claimed(msg.sender, _amount);
    }

}

interface ImetadataContract {
    function fetchMetadata(uint256 tokenId) external view returns (string memory);
}

Hi, the error is not that is not finding Context, can you share the full error when using the overrides and the full error when no using it?

 Error  Command failed: npx hardhat compile
TypeError: Function needs to specify overridden contract "Context".
  --> contracts/riotpass.sol:47:39:
   |
47 |   function _msgSender() internal view override(Context, ERC2771Context)
   |                                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Note: This contract:
  --> @thirdweb-dev/contracts/openzeppelin-presets/utils/Context.sol:16:1:
   |
16 | abstract contract Context {
   | ^ (Relevant source part starts here and spans across multiple lines).


TypeError: Invalid contract specified in override list: "Context".
  --> contracts/riotpass.sol:47:39:
   |
47 |   function _msgSender() internal view override(Context, ERC2771Context)
   |                                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Note: This contract:
  --> @openzeppelin/contracts/utils/Context.sol:16:1:
   |
16 | abstract contract Context {
   | ^ (Relevant source part starts here and spans across multiple lines).


TypeError: Function needs to specify overridden contract "Context".
  --> contracts/riotpass.sol:52:37:
   |
52 |   function _msgData() internal view override(Context, ERC2771Context)
   |                                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Note: This contract:
  --> @thirdweb-dev/contracts/openzeppelin-presets/utils/Context.sol:16:1:
   |
16 | abstract contract Context {
   | ^ (Relevant source part starts here and spans across multiple lines).


TypeError: Invalid contract specified in override list: "Context".
  --> contracts/riotpass.sol:52:37:
   |
52 |   function _msgData() internal view override(Context, ERC2771Context)
   |                                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Note: This contract:
  --> @openzeppelin/contracts/utils/Context.sol:16:1:
   |
16 | abstract contract Context {
   | ^ (Relevant source part starts here and spans across multiple lines).


Error HH600: Compilation failed
For more info go to https://hardhat.org/HH600 or run Hardhat with --show-stack-traces

hi this the full error that I received

This is just a mismatch between versions of the contracts used by thirdweb and the OpenZeppelin contracts.

Try using this:

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

import {ERC721Base} from "@thirdweb-dev/contracts/base/ERC721Base.sol";
import {Context} from "@thirdweb-dev/contracts/openzeppelin-presets/utils/Context.sol";
import {ERC2771Context} from "@thirdweb-dev/contracts/openzeppelin-presets/metatx/ERC2771Context.sol";

interface ImetadataContract {
    function fetchMetadata(
        uint256 tokenId
    ) external view returns (string memory);
}

contract RiotPass is ERC2771Context, ERC721Base {
    address public metadataContract;

    constructor(
        string memory _name,
        string memory _symbol,
        address _royaltyRecipient,
        uint128 _royaltyBps,
        address[] memory _trustedForwarder
    ) ERC721Base(_name, _symbol, _royaltyRecipient, _royaltyBps) ERC2771Context(_trustedForwarder) {
        //trustedForwarder = _trustedForwarder;
        //_setTrustedForwarder(forwarder_);
    }

    //string public versionRecipient = "2.2.0";

    function _msgSender()
        internal
        view
        override(Context, ERC2771Context)
        returns (address sender)
    {
        sender = ERC2771Context._msgSender();
    }

    function _msgData()
        internal
        view
        override(Context, ERC2771Context)
        returns (bytes calldata)
    {
        return ERC2771Context._msgData();
    }

    event claimed(address claimant, uint256 amount);

    function setTrustedForwarder(
        address _trustedForwarder
    ) external onlyOwner {}

    /**
     * @dev override the tokenURI function
     */
    function tokenURI(
        uint256 tokenId
    ) public view virtual override returns (string memory) {
        require(tokenId <= _totalMinted(), "Token ID does not exist");
        return ImetadataContract(metadataContract).fetchMetadata(tokenId);
    }

    /**
     * @dev setting function to call onchain metadata
     */
    function setMetadataAddress(address _address) external onlyOwner {
        metadataContract = _address;
    }

    /**
     * @dev we set claim for our users to get NFT
     */
    function claim(uint256 _amount) public {
        //require(_amount > 0 && _amount < 6);
        _safeMint(msg.sender, _amount);

        emit claimed(msg.sender, _amount);
    }
}

Although it compiles, I don't give any guarantee of its functionality.
Best!

1 Like