I have an erc721a contract and I want to learn how to let users stake their token. I've tried using examples on github but it never recognizes the token ID being staked. Am I doing something wrong? Do I need to add something to the original contract?

3 Answers 3


Well you have two main ways to go about it. Custodial and non custodial.


The way to write a non custodial staking is simply to incorporate it in the same contract as your NFT.

something like:

import "./ERC721.sol"

 contract Staking is ERC721([name],[symbol]) { ...

That will allow you to do whatever you want, like batch staking, overriding the transfer function to prevent the NFT from being transfered while it is staked ... and much more (the sky is the limit) ... And all this while the NFT never leaves the users' wallet.


This is the solution if you want to build a staking for an already deployed NFT contract. In this case you'd have to send the NFT to the staking contract and:

  1. to use the onERC721Received function
  2. to track the owner of the token (since the NFT is now on the contract it doesn't really belong to it's owner, that's why it is called custodial.

To stake your token you could go about in two ways. Receive the token, track ownership of it and then have the owner manually stake it, or have the token immediately staked upon reception (and inside that function you actually establish ownership).

If you did it upon reception the receive function would look something like:

   function onERC721Received(
    address operator, 
    address from, 
    uint256 tokenId, 
    bytes calldata data) external override returns (bytes4) {  
      _stake(tokenId, from);
      return IERC721Receiver.onERC721Received.selector;

   // ***WARNING:*** in here it can receive NFTs from any contract. Be sure to have a a way to either allow only one contract or to track the different collections !!

   function _stake(uint _tokenId, address _from) private {}

I'd recommand having a mapping of owner and possibly a struct of the staking infos of the user. Something like:

mapping(uint => address) private _ownerOf;   // id to owner
mapping( address => StakingInfo) public stakingInfo; // owner to its infos

    struct StakingInfo {
      uint stakedNftNum; //Number of tokens from owner currently in staking
      uint claimed; // All ERC20 claimed by user until now
      uint lastClaim; //Last time of ERC20 claim
      mapping(uint => uint) tokenIds; // a list of tokenIds by index. Use stakedNftNum to enumerate all tokenIds in staking.

And at the unstaking you calculate the rewards, mint the ERC20 in the users' wallet and transfer back the NFTs to their rightful owners.

Well that's just my way to do it quickly , i'm sure it can be improved and optimized.Hope that helps.


An erc721 stake contract should have erc721holder(receiver and 165) Github Openzeppelin Library. you can look at solidity-by-example stake for stake contract but you should change IERC20 with IERC721.

  • Thank you! but when I try to stake a token I get the error "Error provided by the contract: TransferCallerNotOwnerNorApproved Parameters: {}"
    – Callum
    Commented May 13, 2022 at 9:54
  • Before transferring erc721 to a contract, you need to approve it from erc721.
    – prenszuko
    Commented May 13, 2022 at 12:09

I have written a most minimalistic ERC721 staking contract here for gas comparison tests. It currently doesn't mint tokens (will have to uncomment line 63 with a valid ERC20 token for that) to not bias the tests. This also requires the approval by the user for letting the contract handle NFTs (erc721.setApprovalForAll(stakingContract.address, true)).

I'm not sure what you mean by "it never recognizes the token ID being staked", but maybe looking at that will clarify some things.

  • Thank you so much! But when I try to stake a token I get the error "Error provided by the contract: TransferCallerNotOwnerNorApproved Parameters: {}"
    – Callum
    Commented May 13, 2022 at 9:53
  • I mentioned that this requires the approval for the staking contract. By the way, the staking contract can also directly be the erc20 token, thus you could call _mint directly (for claiming rewards).
    – phaze
    Commented May 13, 2022 at 10:12
  • Oh I understand now, thank you so much!!
    – Callum
    Commented May 13, 2022 at 10:34

Your Answer

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

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