How to extend ERC721 with the new ERC721Votes?

Gm, this is my first question here but the forum has been immensely helpful to me so far :slight_smile:

I'm trying to quickly spin up an implementation of ERC721 with the ERC721Votes extension from v4.5.0 (I wish to use this to build a NFT-based Governor):

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

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/draft-ERC721Votes.sol";

contract NFTName is ERC721("NFTName", "NFTSYMBOL"), ERC721Votes {


I get the following compiler message: Derived contract must override function "_afterTokenTransfer". Two or more base classes define function with same name and parameter types. After following the message's advice and modifying the code to override _afterTokenTransfer:

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

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/draft-ERC721Votes.sol";

contract NFTName is ERC721("NFTName", "NFTSYMBOL"), ERC721Votes {
    // added
    function _afterTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) internal override(ERC721, ERC721Votes) {
        super._afterTokenTransfer(from, to, tokenId);

I get the following message, which I don't know how to resolve:

Contract "NFTName" should be marked as abstract.

Would appreciate some feedback on how to get this compiling. Using hardhat v2.8.4 on Node v16.6.1.

[[Marking as resolved]]

I was able to find a mock contract for my use case in the OpenZeppelin repository. It can be found here.

1 Like

Hi @atharvashukla,

You post was very helpful.

Do you know if we also need to include a TimeLock.sol file to make ERC-721 voting work?

If so, how is the Governor contract "aware" that it exists and it's location? I couldn't find any clear examples!

For setting up ERC721Votes you can get some help from Contracts Wizard:

For setting up the Governor you can also use Contracts Wizard, and the resulting contract receives the address of the voting token in the constructor. Our guide How to set up on-chain governance will probably also be helpful.

Hi Frangio,

I've been testing the wizard for over a week. And read through most of open Zeppelin's docs on governance and ERC721 tokens.

Unfortunately there's nothing specifically on how to implement governance for ERC 721 tokens.

Is an additional interface needed to connect or link the ERC 721 token to the governance contract?

right now they are very much independent modules in the wizard.

Hi @vanessacodes

As far as I know, you don't NEED a TimeLock to make Governance work (But there is an option to add that and is used quite often).

As for how does the Governor know about the ERC721... the constructor of the Governor contract will have an IVotes _token parameter. You can go to the Governor tab on the Wizard and click on ERC20Votes radio button (they should rename this to just "Votes"), if you look at the code, you'll see the constructor parameter there. As long as your ERC721 is a ERC721Votes, then it will also implement IVotes, so once you pass it as a parameter, it will be "aware" that it exists and what its location is.


I don't see the "Votes" option on ERC721 tab. I do see it on ERC20 under the features subheading.


You would need to include a timelock file and a governor file that inherits from openZeppelin governance

This is it! We forgot to rename that option but it also works for ERC721Votes.

My bad! We haven't updated it yet. I've created an issue, you can subscribe to hear once it's added. Will try to get this out soon.


@frangio @atharvashukla @YummyDao

I'm using the MyGovernor generated from the wizard

Do we need to make any modifications to our ERC721Votes minter contract or to MyGovernor.sol for them to work together?

Regarding TimeLock.sol: Are their any guides or tutorials on what to put in it?

If you selected "ERC20Votes" in the Wizard Governor it will work when you connect it to an ERC721Votes token.

TimeLock.sol is not an OpenZeppelin Contract. Our timelock contract is called TimelockController. This is described in the guide I linked previously.

Ask your questions about the timelockcontroller and I would give you the parameters you need in the constructor, when deploying

@frangio thanks for the follow-up. I'm new to solidity and want to learn!

  1. ERC721Votes: I posted a specific question along with my broken ERC721VotesMock.sol contract on a new thread: How to Fix conflict with ERC721Votes and ERC721Enumerable

Error is identified. I just don't know how to fix. I know you're busy, but would really appreciate your help. Perhaps you can share a solution?

  1. MyGovernor.sol from the Wizard: Any idea of what additions to MyGovernor.sol would be needed for it to work with my ERC721Votes token mentioned in 1 above?? I'm thinking I need to add this:

IERC721Enumerable token = IERC721Enumerable( -- ERC721VotesMock.sol Contract Address --);

Also, do I need to make it abstract contract too?

If none of the above, how is it "connected and aware" of my ERC721Votes token?

  1. Timelock: I thought I needed a separate TimeLock.sol file. Apparently I don't. So I can interact directly with TimelockController.sol by means of MyGovernor.sol mentioned it point 2 above, right?.

I will re-read the Governance doc you mentioned. Maybe I overlooked something during my first read of it!

When you generate your Governor using Wizard, there is a "_token" constructor argument. When you deploy the governor you have to pass in the address of your ERC721Votes contract that you deployed previously.


Thank you so much for answering my question. I had no idea about that!

This is an awesome modular design for the Governor!


I finally found the OpenZeppelin TimelockController.sol template:

So, if I understand everything correctly, after deploying my ERC721VotesMinter.sol and the TimelockController.sol template, I just manually update MyGovernor.sol with the following:

constructor (IVotes /* ERC721VotesMinter.sol Contract Address Goes Here */, TimelockController /* TimelockController.sol Address Goes Here*/)

And then deploy MyGovernor.sol! Is this right?

Finally, if I send funds to the deployed TimelockController.sol template, a vote is needed to withdraw and send funds to another address, right?

Or can I as the deployer (and funder of TimelockController.sol) unilaterally withdraw funds?

If you want to add the timelock as the treasurer or you can still get a multisig to do that