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?
Code to reproduce
Relevant contracts and transaction:
This is an example of a meta transaction used to create a collection:
https://polygonscan.com/tx/0xac1896b6e72786a581c5f106156bff290323aa5ee80cb36b4e588c835a55e216
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
Forwarder.execute
function execute(
ForwardRequest calldata req,
bytes calldata signature
) external payable returns (bool, bytes memory) {
require(
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 {
invalid()
}
}
return (success, returndata);
}
RevenueSplitter.createCollection
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) {
require(
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(
owner(),
minter,
address(this),
name,
symbol,
royaltyAmount,
baseURI
);
_collections[_collection].registered = true;
_collections[_collection].totalShares = 0;
emit CreateCollectionEvent(address(_collection));
_addCollectionPayees(address(_collection), _payees, _shares);
return address(_collection);
}
CollectionFactory
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));
collection.initialize(
_admin,
_minter,
revenueSplitter,
name,
symbol,
royalty,
baseURI
);
return clone;
}