I have a smart contract RevenueSplitter which is used to create ERC-721 contracts via a CollectionFactory using the minimal proxy pattern. We recently switched to using meta transactions to initiate contract creation. One unintended side effect of this is that the relayer is now listed as the contract creator for the ERC-721 contract which affects who can manage the contract on OpenSea.

Meta transactions are issued through openzeppelin relayers / auto-tasks and verified by a Forwarder contract before calling the RevenueSplitter with the encapsulated transaction.

The autotask sends an Execute transaction to Forwarder contract which then calls createCollection on the RevenueSplitter contract. The RevenueSplitter then calls createCollection on the CollectionFactory contract which creates the actual collection using a minimal proxy pattern (OpenZeppelin Clones).

What I would like to accomplish is that the contract creator be the from address in the meta transaction rather than the relayer's address. Is this possible, and if yes, what changes do I need to make?

Relevant contracts and transaction:

This is an example of a meta transaction used to create a collection:


The Forwarder is https://polygonscan.com/address/0x0ee7dd0f427077922918ef836d2e80aa7fdcc717

The RevenueSplitter is https://polygonscan.com/address/0xfcd4d3faa3c7d7f1c61e9d1dc8391c96a55675d8

The CollectionFactory is https://polygonscan.com/address/0x27478bdeb95673400207ba78a2cd3973f1064967

The resulting ERC-721 contract is https://polygonscan.com/address/0xa4708ce0adb069ade32854fabb759831b6120b5e

Relevant code


  function execute(
        ForwardRequest calldata req,
        bytes calldata signature
    ) external payable returns (bool, bytes memory) {
            verify(req, signature),
            "REMXForwarder: signature does not match request"
        _nonces[req.from] = req.nonce + 1;

        uint256 gasForTransfer = 0;

        (bool success, bytes memory returndata) = req.to.call{
            gas: req.gas,
            value: req.value
        }(abi.encodePacked(req.data, req.from));

        require(success, "REMXForwarder: call failed");

        if (gasleft() + gasForTransfer <= req.gas / 63) {
            /// @solidity memory-safe-assembly
            assembly {
        return (success, returndata);


    function createCollection(
        string memory name,
        string memory symbol,
        uint256 royaltyAmount,
        address[] memory _payees,
        uint256[] memory _shares,
        string memory baseURI,
        address minter
    ) external override returns (address) {
            hasRole(CREATE_COLLECTION_ROLE, _msgSender()) ||
                owner() == _msgSender(),
            "RMX: caller is not owner or collection creator"

        require(bytes(name).length > 5, "RMX: Name too short");
        require(bytes(symbol).length >= 3, "RMX: Symbol too short");
        require(royaltyAmount <= 100, "RMX: Invalid royalty amount");

        address _collection = _collectionFactory.createCollection(

        _collections[_collection].registered = true;
        _collections[_collection].totalShares = 0;

        emit CreateCollectionEvent(address(_collection));

        _addCollectionPayees(address(_collection), _payees, _shares);

        return address(_collection);


    constructor() {
        implementation = address(new REMXCollection());

    function createCollection(
        address _admin,
        address _minter,
        address revenueSplitter,
        string memory name,
        string memory symbol,
        uint256 royalty,
        string memory baseURI
    ) external override returns (address) {
        address clone = Clones.clone(implementation);
        REMXCollection collection = REMXCollection(payable(clone));
        return clone;

2 Answers 2


The answer, after consulting several different forums and then thinking deeply about how this works, is that you cannot change the apparent from address for a transaction in the way I was thinking. However, there is a way to fix how OpenSea interprets the ownership of a contract by implementing an owner method which, for me, was as simple as adding OpenZeppelin's Ownable to our collection contract. This is, in fact, a better solution in that it allows transferring ownership at a later time if desired!


Try using the Biconomy's BasicMetaTransaction.sol file . I think that might solve your problem.

  • I looked into this, it seems to be using the same basic mechanism that I am already employing. I am starting to come to the conclusion that this may not be possible. Aug 28 at 18:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.