From my last post about NFT staking contracts, I received a message on Twitter from a developer. That developer was creating a staking contract for an NFT project but was getting the following error:
“ERC721: transfer caller is not owner nor approved”. I immediately recognized the error due to stumbling into this myself previously. So we had a conversation about token transfer and approval; this is the takeaway.
Users must grant permission via approval when transferring ERC721 or ERC1155 tokens from users to your contracts, such as marketplaces or staking/escrow contracts.
Who can approve? When does it occur? Where does it come from? Why is this the way?
There are two functions that’ll get the job done, setApprovalForAll() and approve().
The OpenZeppelin documentation describes approve(address to, uint256 tokenId) as:
“Gives permission to transfer tokenId token to another account. The approval is cleared when the token is transferred.”
And setApprovalForAll(address operator, bool _approved) as:
“Approve or remove operator as an operator for the caller. Operators can call transferFrom() or safeTransferFrom() for any token owned by the caller.”
In other words, approve() grants permission to transfer a single token, while setApprovalForAll() grants permission to transfer many/all tokens of a specified contract.
By default, tokens can only be transferred from an address if it’s the owner.
Typically your front end will trigger the call for approval. This transaction needs to occur before attempting a token transfer to your staking contract and will initiate a separate transaction than your
transferFrom() call which can occur afterward.
We are able to initiate a token approval because they live on the token contract, as it’s part of the ERC721 and ERC1155 token standards.
This means that any NFT token that is compliant with one of those two standards will have a function to call for approvals.
Security! As a user, you don’t want anyone to transfer tokens out of your wallet without your permission.
However, with permission, you want certain approved parties to have access to your assets and operate on your behalf.
Approvals may also be revoked in the future.
Summary of Steps
Assuming a user hasn’t already approved and transferred tokens to your contract, the overall order of operations from a user goes as follows:
- The user clicks a button on your front end.
- onClick() prompts user to setApprovalForAll(YOUR_CONTRACT_ADDRESS, true);
- The user signs approval transaction
- onClick() prompts user to sign deposit() transaction
- User signs transaction and token is transferred to your contract.