0

I'm trying to make a contract that will allow the owner of an erc721 token (from a different contract) send their nft to this new contract.

For example: I have a basic erc721 minting contract:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "hardhat/console.sol";

contract NFT is ERC721, Ownable {
    using Counters for Counters.Counter;
    Counters.Counter private _nftIds;

    string public URI;

    mapping(uint256 => address) public _getNftOwnerId;
    mapping(address => mapping(uint256 => uint256)) public _usersNfts;
    uint256[] public _allNfts;
    constructor() ERC721 ("Creators Never Die Moodboards", "CND") {}

    function mint() public returns(uint256) {
        uint256 newNftId = _nftIds.current();
        _safeMint(msg.sender, newNftId);

        _getNftOwnerId[newNftId] = msg.sender;
        _usersNfts[msg.sender][newNftId] = newNftId;

        _allNfts.push(newNftId);
        _nftIds.increment();

        console.log("nft minted with id", newNftId);
        return newNftId;
    }



}

Now I have a separate contract where I'm trying to allow the msg.sender send their owned tokenId to this new contract.

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "hardhat/console.sol";

contract BulkTransfer {
    using Counters for Counters.Counter;
    Counters.Counter private _nftIds;


    constructor() {}

    function approveContract(address _contractAddress) external {
        ERC721 token = ERC721(_contractAddress);
        require(token.balanceOf(msg.sender) > 0, "Caller must own nft");
        token.setApprovalForAll(address(this), true);
    }

    function bulkTransfer(address _contractAddress, uint256 _tokenId) external {
        ERC721 token = ERC721(_contractAddress);


        require(token.balanceOf(msg.sender) > 0, "Caller must own nft");
        require(token.ownerOf(_tokenId) == msg.sender, "You must own the token");

        token.transferFrom(msg.sender, address(this), _tokenId);

    }





}

When I call the bulkTransfer function I keep getting the error: "ERC721: transfer caller is not owner nor approved".

When I call aproveContract function I get "ERC721: approve to caller"

I'm doing this all in remix ide.

3 Answers 3

0

The line:

token.transferFrom(msg.sender, address(this), _tokenId);

requires that msg.sender approved the contract, where BulkTransfer is deployed, to transfer msg.sender's tokens. There is no such approve, so it results with: "ERC721: transfer caller is not owner nor approved".

Regarding "ERC721: approve to caller" Take a look at your call setApprovalForAl from OpenZeppelin:

function setApprovalForAll(address operator, bool approved) public virtual override {
    _setApprovalForAll(_msgSender(), operator, approved);
}

function _setApprovalForAll(
    address owner,
    address operator,
    bool approved
) internal virtual {
    require(owner != operator, "ERC721: approve to caller");
    _operatorApprovals[owner][operator] = approved;
    emit ApprovalForAll(owner, operator, approved);
}

msg.sender for the approval is the contract, and adress(this) is also the contract, you are breaking the require statement

require(owner != operator, "ERC721: approve to caller");
3
  • I have a function called approveContract
    – Dileet
    Feb 2, 2022 at 21:21
  • Please check the edit.
    – v1bio
    Feb 2, 2022 at 21:39
  • Isn't msg.sender the person executing the transaction? How do I get the wallet address of the person executing the transaction then?
    – Dileet
    Feb 2, 2022 at 21:45
0

NFT and tokens, in general, don't work that way.

When you transfer a token to an address (Externally Owned Account or Contract Account), you update values in the smart contract controlling that token.

Looking at your code, in the bulkTransfer function you are trying to assign tokens (CND) owned by the sender to the BulkTransfer contract.

If this is what you want to do, you simply need to call the transfer method in the CND smart contract.

Please note that using standard implementations like the ones provided by OpenZeppelin, if you send tokens to an address controlled by a smart contract and not an EOA, you lose those tokens forever.

If instead, you want the user to be able to send its own tokens to multiple addresses, and the CND contract hasn't a bulk transfer function implemented, you can write and deploy a smart contract to do that.

It's not very cheap to use that kind of contact, so I still don't see exactly why you need it, but this is what you need to do.

Your bulkTransfer must be changed to get at least a recipient parameter and a list of tokenIds, then you must loop through all the selected tokenIds, and for each you must check for allowance (getApproved) and transfer it calling something like token.transferFrom(msg.sender, recipient, _tokenId).

And before calling the bulkTransfer method in BulkTransfer contract, the owner must send a lot of approval transactions (approve(address _approved, uint256 _tokenId) to the CND contract specifying the BulkTransfer address in the _approved parameter, or the entire bulkTransfer method will fail, even if you fail a single approval out of 100.

4
  • I'm trying to write a contract that I can send a bunch of nfts to, then have another wallet claim those nfts from this separate contract.
    – Dileet
    Feb 2, 2022 at 21:49
  • Why do you need two separate contracts? Why don't you implement the bulk method in the NFT contract directly? Do you want to interact with other (not deployed by you) contracts? Feb 2, 2022 at 22:03
  • Yes correct. I need to interact with other contracts that aren't deployed by me. There must be a way. Open Sea transfers nfts when a purchase is complete. Or rainbow wallet allows you to send nfts you own to another address.
    – Dileet
    Feb 2, 2022 at 22:05
  • You can interact without problem with other smart contracts, your exact goal is what matters :) I tried to elaborate more on the response, it's difficult to do it in the comments. Feb 2, 2022 at 22:37
0

This function on BulkTransfer does nothing.

function approveContract(address _contractAddress) external {
    ERC721 token = ERC721(_contractAddress);
    require(token.balanceOf(msg.sender) > 0, "Caller must own nft");
    token.setApprovalForAll(address(this), true);
}

specifically, this line:

token.setApprovalForAll(address(this), true);

the BulkTransfer contract is calling the setApprovalForAll function on the token contract, and approving itself for all, since msg.sender is the BulkTransfer contract in that case.

However, you want to approve it for all your tokens, so just execute this on the NFT contract:

 setApprovalForAll(BulkTransfer_address_here, true);

then the BulkTransfer contract will be able to transfer your tokens until you unapprove it.

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.