I have an ERC20 token that I would like another smart contract (lets call it Manager) to be able to mint to the users of the dapp. I am assuming, I should restrict the mint function to only be execute by the Manager smart contract otherwise anyone can just go to the token address and mint from etherscan. First of all, is that assumption correct?

Based on that assumption, my token contract looks like this:

contract XYZToken is ERC20, ERC20Burnable, Ownable, {
    constructor() ERC20("XYZToken", "XYZ") ERC20Permit("XYZToken") {}

    function mint(address to, uint256 amount) external onlyOwner {
        _mint(to, amount);

Then in Manager contract, I would need the address of this token to be able to mint so I am passing that in the constructor

contract Manager {

    XYZToken xyzToken;

    constructor(address _xyzToken) {
        xyzToken = XYZToken(_lendToken);

    function mintTokens(uint256 _amount) external nonReentrant {
        xyzToken.mint(msg.sender, _amount);

Finally, to make the Manager contract owner of the Token contract, I have to run this deployment script after deployment of both contracts

    const manager = await ethers.getContract("Manager");
    const token = await ethers.getContract("XYZToken");

    await token.transferOwnership(manager.address);

Is this the right way to set up the minting of tokens through another smart contract or is there a better design pattern for this approach?

Would it be better to use a modifier onlyManager instead of onlyOwner on token mint function? then I have to pass the Manager contract address to the token after deployment. At the same time, the Manager needs the address of the token during its deployment so it becomes this cyclic dependency that won't allow deployment to happen. I guess another approach would be to set the manager public property on the token after deployment similar to the transferOwnership.

Finally, should the Manager be minting token through the single reference of the token address passed to it or instantiate the token object using the new operator?

Overall, I am looking for suggestions on the correct design pattern or approach for this use case. Any help is appreciated!

  • Hi there. Just wondering what is the rationale behind using a smart contract manager for this implementation. Have you considered using a whitelist in your smart contract?🤔 Jan 26 at 5:51
  • Hi, the rationale is that tokens should be issued as a result of performing certain activities within the protocol as reward or governance tokens. Either way, the user shouldn't be able to mint the tokens themselves as they represent a sorta membership or achievement status within the application. The manager acts as the gatekeeper to ensure the right amount of tokens are issued to the right recipients. Whitelist wont work because the token recipients can be anyone and not known ahead of time. Jan 26 at 12:42
  • If you are asking for design approaches then I would recommend you to check sources of already existing contracts. There are lots of projects you can learn: Augur, UniswapV3, Balancer V2, ENS (Ethereum (internal) Doman Names), CrytoKitties. And there are lots and lots more of them. All these contracts are made by the pros, so it is a good learning resource. Augur is the most complex of those I listed
    – Nulik
    Jan 26 at 15:22
  • Sure, will look into it. Thanks for the suggestion. Also, I will keep an eye on this thread for any other suggested design patterns for this use case. Jan 26 at 17:17

1 Answer 1

contract XYZToken is ERC20, ERC20Burnable, Ownable, {
    constructor(address _admin) ERC20("XYZToken", "XYZ") ERC20Permit("XYZToken") {
 admin = _admin

modifier onlyAdmin {
 if(msg.sender !== admin){
  revert('Only admin')

address admin;

    function mint(address to, uint256 amount) external onlyOwner {
        _mint(to, amount);
  • Thanks for your suggestion. So your suggestion is to set an admin during creation. That’s fine but keep in mind, the admin is not an EOA, it’s the Manager smart contract. And the manager smart contract needs reference to this token in its constructor to be able to call the mint function. If I attempt to pass the manager as admin for token constructor then it’s a cyclic dependency for deployment. I mentioned this complication in my post. An alternative would be to set admin as a public property on token after deployment. Jan 26 at 15:25
  • Create a interface for the token an add It on the manager. Jan 26 at 17:17
  • Even with the interface, you still need the address of the deployed contract for the token. IXYZToken(tokenAddress).mint(...) Jan 26 at 17:24
  • You can catch the address of the token emitting an event with it and then stores somewhere. Jan 27 at 22:37

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.