Hello,
I am currently working on an ERC721A implementation that utilizes ERC2771ContextUpgradeable
for handling meta-transactions. I have a few questions regarding my approach and some related functionalities:
-
Implementation Approach: I would like to confirm if my implementation of the ERC721A contract with
ERC2771ContextUpgradeable
is correct. Specifically, I am using the constructor to set the trusted forwarder directly. Is this the recommended way to do it? -
Purpose of ERC2771ForwarderUpgradeable: What is the actual purpose of the
ERC2771ForwarderUpgradeable
contract? If I can already set a forwarder (at contract deploy time) by implementingERC2771ContextUpgradeable
, what additional benefits does the forwarder provide? -
Transfer Functionality without forwarder: if i'm using ERC2771ContextUpgradeable with _msgSender overriding how can I implement a transfer method in my SC where gas fees are paid by users instead of the forwarder? (I need a transfer where the fees are paid by the forwarder and one where they are paid by the forwarder)
My Implementation
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
import "erc721a-upgradeable/contracts/ERC721AUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/metatx/ERC2771ContextUpgradeable.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "./lib/ERC721AURIStorageUpgradeable.sol";
contract ERC721ACustom is
ERC721AUpgradeable,
ERC721AURIStorageUpgradeable,
AccessControlUpgradeable,
ERC2771ContextUpgradeable
{
using Strings for uint256;
/* ========== STATE VARIABLES ========== */
// URI
string private _contractURI;
// ROLE
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
/* ========== EVENTS ========== */
event Mint(address _from, uint256 quantity);
/* ========== CONSTRUCTOR ========== */
constructor(address trustedForwarder) ERC2771ContextUpgradeable(trustedForwarder) {
_disableInitializers();
}
/* ========== INITIALIZER ========== */
function initialize(
string memory _tokenName,
string memory _symbol,
string memory contractURI_,
uint256 _collectionSize,
address _collectionOwnerAddress
) public initializerERC721A initializer {
require(_collectionOwnerAddress != address(0), "ERC721ACustom: owner address not valid");
__ERC721A_init(_tokenName, _symbol);
__AccessControl_init();
__ERC721URIStorage_init();
MAX_COLLECTION_SUPPLY = _collectionSize;
_contractURI = contractURI_;
_grantRole(DEFAULT_ADMIN_ROLE, _collectionOwnerAddress);
_grantRole(MINTER_ROLE, _collectionOwnerAddress);
}
/* ========== MODIFIER ========== */
modifier _requireMint(uint256 _quantity) {
if (tx.origin != _msgSender()) revert OnlyHumans();
if (_totalMinted() + _quantity > MAX_COLLECTION_SUPPLY) revert MintingFinished();
_;
}
/* ========== LOGIC ========== */
function mintToken(
uint256 _quantity,
address _to,
string[] memory _URIs
) external _requireMint(_quantity) onlyRole(MINTER_ROLE) {
require(_quantity == _URIs.length, "ERC721ACustom: quantity and URIs length mismatch");
uint256 startTokenId = _nextTokenId();
_safeMint(_to, _quantity);
for (uint i = 0; i < _URIs.length; i++) {
_setTokenURI(startTokenId + i, _URIs[i]);
}
emit Mint(_msgSender(), _quantity);
}
function _beforeTokenTransfers(address from, address to, uint256 startTokenId, uint256 quantity) internal override {
super._beforeTokenTransfers(from, to, startTokenId, quantity);
}
/* ========== VIEWS ========== */
function isApprovedForAll(address _owner, address _operator) public view override returns (bool) {
return super.isApprovedForAll(_owner, _operator);
}
function tokenURI(
uint256 tokenId
) public view override(ERC721AUpgradeable, ERC721AURIStorageUpgradeable) returns (string memory) {
return super.tokenURI(tokenId);
}
function contractURI() external view returns (string memory) {
return _contractURI;
}
function maxSupply() external view returns (uint256) {
return MAX_COLLECTION_SUPPLY;
}
function _burn(uint256 tokenId) internal override(ERC721AUpgradeable, ERC721AURIStorageUpgradeable) {
super._burn(tokenId);
}
function _msgSender() internal view virtual override(ContextUpgradeable, ERC2771ContextUpgradeable) returns (address sender) {
return super._msgSender();
}
function _msgData() internal view virtual override(ContextUpgradeable, ERC2771ContextUpgradeable) returns (bytes calldata) {
return super._msgData();
}
function _contextSuffixLength() internal view virtual override(ContextUpgradeable, ERC2771ContextUpgradeable) returns (uint256) {
return super._contextSuffixLength();
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(
bytes4 interfaceId
) public view override(AccessControlUpgradeable, ERC721AUpgradeable) returns (bool) {
return interfaceId == type(IERC721AUpgradeable).interfaceId || super.supportsInterface(interfaceId);
}
}
I appreciate any insights or guidance you can provide on these topics. Thank you! Best regards.