Token Approval does not work!

:1234: Code to reproduce


:computer: Environment

I'm writing a smart contract using ERC721
when I use
IERC721Upgradeable(address(this)).approve(address(this), newTokenId);

Whenever I use this line of code to approve of a token to a contract, it never works and throws some random error.
If I use
approve(address(this), newTokenId);

that always works but I can't set my personal contract to be the operator of a token in a different contract.
My question is why does IERC721(tokenAddress) work on all functions in there except: approve, setApprovalForAll!!

You should use the internal function _approve.

What If I want to set approval for a user that has minted the nft in a different smart contract? and I want approval to be able to work with their token?

In the case of other tokens where you're not the developer, it is the token holder that needs to invoke the approve function through a transaction that they send. This is the wait that they express consent to potentially having the token taken away.

I understand that but when I write a function that asks the owner to approve me to be the operator of the token the functions fails even though the message.sender is the owner of the token, I will link below my code.

  function setApproved(address tokenAddress, uint256 tokenID) public {
      IERC721Upgradeable(tokenAddress).setApprovalForAll(address(this), true);
  }

this is a simple version but this function simply does not work once setApproval has been called once, trying to setApproval for a different operator just does not work.
I'm just wondering why is that happening?

No, this is not right. The token owner must invoke setApprovalForAll by directly interacting with the token contract. When you wrap it in a setApproved function what you're doing is that your contract becomes the msg.sender.

oh, that was actually an error I kept running into, but then how do I fetch the NFT's contract from someones wallet, I know how I can fetch the contracts address now how can I fetch the contract itself?

Sorry I don't understand this question. Feel free to describe in more detail.

Hi @frangio
I am trying to use setApprovedForAll() followed by a safeTransferFrom() but keep getting an error when trying to get approval from an address to transfer IERC1155 tokens created on Opensea to my smartcontract and then to another address.

this is the error:

"execution reverted: AssetContractShared#creatorOnly: ONLY_CREATOR_ALLOWED"

I believe it is because OpenZeppelin overwrites their safeTransferFrom function as the tokens come from the Opensea Collectibles contract:(0x2953399124F0cBB46d2CbACD8A89cF0599974963).

Inside of that contract there is a modifier:

modifier creatorOnly(uint256 _id) {
    require(
        _isCreatorOrProxy(_id, _msgSender()),
        "AssetContractShared#creatorOnly: ONLY_CREATORALLOWED"
    );
    ;
}

Any idea on how to get approval to transfer token x from User address X to smart contract S to User Address Y circumventing the above modifier?

These two operations are not done by the same contract, right?

  • setApprovedForAll must be called by the token owner
  • safeTransferFrom must be called by the approved address, to take ownership of the token

A contract X cannot just take ownership of someone's assets without this personnel explicitly approving the transfer.

@Amxx
I am calling setApprovalForAll() like so:

await OpenseaContract.setApprovalForAll(
      mySmartContractAddress,
      true
    );

and the user has to sign this approval.

and safeTransferFrom() like so:

  const transferFromOwnerToMarketplace = await OpenseaContract.safeTransferFrom(
    mySmartContractAddress,
    aDifferentUserAddress,
    tokenId,
    1,
    "0x"
   );

but Opensea's safeTranferFrom() seems to have a modifier that prevents the isApproved address (which should be mySmartContractAddress) to transfer the tokens, as documented here as well.

So I was wondering if there was a known workaround?

In the first function, the user is approving mySmartContractAddress to operate on the assets it holds on OpenseaContract

The second operation, the caller (same user?) is asking to transfer a token held by mySmartContractAddress to aDifferentUserAddress.

That is not consistent. You have to be clear about who owns the assets (that person will need to do the approval) and who is calling transferFrom (that person must receive the approval, unless its the owner, then no approval is needed)

@Amxx Thank you so much, that makes a lot of sense,

The following code works:

  const transferFromOwnerToMarketplace = await openseaContract.safeTransferFrom(
    tokenOwnerAddress,
    aDifferentUserAddress,
    tokenId,
    1,
    "0x"
  );

the token is transferred directly from the owner to the other user and does not require setApprovalForAll, but requires the user to sign the transaction.

Following your advice, if I want mySmartContract to transfer the token on behalf of the user, I would do:

  if (!(await contract.isApprovedForAll(tokenOwnerAddress, mySmartContractAddress))) {
    const setApprovalTransaction = await openseaContract.setApprovalForAll(
      mySmartContractAddress,
      true
    );
    await setApprovalTransaction.wait();
  }

Followed by:

  const transferFromOwnerToMarketplace = await openseaContract.safeTransferFrom(
    mySmartContractAddress,
    aDifferentUserAddress,
    tokenId,
    1,
    "0x"
  );

But this is where I get the error:

reason="execution reverted: AssetContractShared#_requireMintable: ONLY_CREATOR_ALLOWED"

which takes me back to thinking that even if the user has approved mySmartContractAddress to transfer their tokens, the safeTransferFrom in the Opensea Contract does not allow anyone apart from the owner to call safeTransferFrom.

Would that observation be correct?
And if it is correct, would you suggest having the tokenId owner transfer their token to mySmartContract first, for mySmartContract to then transfer to aDifferentUserAddress?

What I am trying to do is have a user transfer their already existing tokens minted on Opensea to another user when a certain condition is met in the future. I cannot have the owner sign safeTransferFrom (because of the 'in the future' component) so I wanted to delegate this to mySmartContract.

Again, same as previously, this call transfers FROM mySmartContractAddress ... but you are saying that the tokenOwner is not the smartcontract ... so that is wrong

See the EIP

    /**
        @notice Transfers `_value` amount of an `_id` from the `_from` address to the `_to` address specified (with safety call).
        @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see "Approval" section of the standard).
        MUST revert if `_to` is the zero address.
        MUST revert if balance of holder for token `_id` is lower than the `_value` sent.
        MUST revert on any other error.
        MUST emit the `TransferSingle` event to reflect the balance change (see "Safe Transfer Rules" section of the standard).
        After the above conditions are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call `onERC1155Received` on `_to` and act appropriately (see "Safe Transfer Rules" section of the standard).        
        @param _from    Source address
        @param _to      Target address
        @param _id      ID of the token type
        @param _value   Transfer amount
        @param _data    Additional data with no specified format, MUST be sent unaltered in call to `onERC1155Received` on `_to`
    */
    function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data) external;