Transferring ERC721 tokens

Hi, I have a question about ERC721 safetransferfrom(address from, address to, uint256 tokenid, bytes memory _data). What’s the “_data” and how can I input data when I transfer ERC721 to a contract address?
BTW,
I have added the onERC721Receiver() in my contract, but fail to transfer ERC721 from my eoa address.
I’m not sure that have I understood the step of interacting contract address with ERC721 token. Are the following steps correct?
1 setting a onERC721Receiver() in contract
2 transfer ERC721 token to the contract. And the caller is the owner of this ERC721 token.
3 The contract will mark the received ERC721 token and deposit it into the contract.

Please tell more information about the ERC721 interaction. Thanks in advance.

2 Likes

Hi @uua,

From the EIP: https://eips.ethereum.org/EIPS/eip-721

/// @param data Additional data with no specified format, sent in call to `_to`

So you can included any additional data you want in here.

Yes.

I would describe it as follows:

The token holder calls safeTransferFrom and if the recipient is a contract it must implement IERC721Receiver and return its Solidity selector to confirm the token transfer.

I would recommend trying it out with a simple example.

I created a simple ERC721 (please note there is no access control on the minting), along with two other contracts, one that is setup to receive ERC721 and the other which isn't.

Using safeTransferFrom works when transferring to MyContract which supports and fails when trying to transfer to MyOtherContract.

GameItem.sol

// contracts/GameItem.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract GameItem is ERC721 {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;

    constructor() public ERC721("GameItem", "ITM") {}

    function awardItem(address player, string memory tokenURI)
        public
        returns (uint256)
    {
        _tokenIds.increment();

        uint256 newItemId = _tokenIds.current();
        _mint(player, newItemId);
        _setTokenURI(newItemId, tokenURI);

        return newItemId;
    }
}

MyContract.sol

// contracts/MyContract.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";

contract MyContract is IERC721Receiver {

    /**
     * Always returns `IERC721Receiver.onERC721Received.selector`.
     */
    function onERC721Received(address, address, uint256, bytes memory) public virtual override returns (bytes4) {
        return this.onERC721Received.selector;
    }
}

MyOtherContract.sol

// contracts/MyOtherContract.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

contract MyOtherContract {

}
truffle(develop)> token = await GameItem.new()
undefined
truffle(develop)> myContract = await MyContract.new()
undefined
truffle(develop)> myOtherContract = await MyOtherContract.new()
undefined
truffle(develop)> token.awardItem(accounts[0],'https://example.com/token/1')
{ tx:
...
truffle(develop)> token.awardItem(accounts[0],'https://example.com/token/2')
{ tx:
...
truffle(develop)> await token.safeTransferFrom(accounts[0], myContract.address, 1)
{ tx:
...
truffle(develop)> await token.safeTransferFrom(accounts[0], myOtherContract.address, 2)
Thrown:
{ Error: Returned error: VM Exception while processing transaction: revert ERC721: transfer to non ERC721Receiver implementer -- Reason given: ERC721: transfer to non ERC721Receiver implementer.
...
4 Likes

Thanks for replying. The problem has been solved.

1 Like

Hey,

If the receiver is just another Ethereum address, then how do we do that?

I wrote this function

    function transfer(address _seller, address _buyer, uint256 _tokenId) external payable returns(bool){
        // transfer assets from seller to buyer
        // seller pays the tx fees        
        address ownerAddress = ownerOf(_tokenId);        
        require(_seller == ownerAddress,"Ownership mismatch");
        approve(_buyer, _tokenId);      
        setApprovalForAll(msg.sender, true);
        transferFrom(ownerAddress, _buyer, _tokenId);
        return true;
    }

Do you think this would work?

Hey @Shahid_Shaikh, the extra _data parameter is only involved in safeTransferFrom, not transferFrom (as you are using on your code). And even if you were using safeTransferFrom (which I’d recommend!), the _data is ignored if the recipient is an EOA (ie an Externally Owned Account, ie not a contract). See this chunk of code from the ERC721 implementation:

Hey,

Something like this?


    function transfer(address _seller, address _buyer, uint256 _tokenId) external payable returns(bool){
        // transfer assets from seller to buyer
        // seller pays the tx fees        
        address ownerAddress = ownerOf(_tokenId);        
        require(_seller == ownerAddress,"Ownership mismatch");
        approve(_buyer, _tokenId);      
        setApprovalForAll(msg.sender, true);
        safeTransferFrom(ownerAddress, _buyer, _tokenId);
        return true;
    }

No matter what I do though, i get this error

Error: (Awaiting internal transactions for reason)

https://explorer-mumbai.maticvigil.com/tx/0xc10b082c7b6c686ed5516eb1ff969c145e4bcebe6c22e0e2b00eed2501ba2aa4/internal-transactions

Any help on this, I am scratching my head s

I’m not exactly sure what you intend to do. If you just want to transfer a token from seller to buyer, why not just issue a transferFrom? Why do you need that transfer function?

Hey,

I want to build a Dapp around it with multiple users, and would like to transfer the NFT from one user to another user.

I created the transfer function so that I can call it using hardhat. Like this.

const hre = require("hardhat");
async function main() {
  const NFT = await hre.ethers.getContractFactory("MyNFT");  
  const WALLET_ADDRESS = "0xB705730CDdDb2Af877A92B8D21bee30A61C0a416";
  const RECIEVER_ADDRESS = "0xDF73E2c9D05bE9894E94dAD0BCc68FEBa4c2C7F9";
  const CONTRACT_ADDRESS = "0x4c70949868D3F28BfCE917e161662C2ed901652b";
  const TOKENID = "1";
  const contract = NFT.attach(CONTRACT_ADDRESS);
  const result = await contract.transfer(WALLET_ADDRESS,RECIEVER_ADDRESS, TOKENID);
  console.log("NFT transferred:", result);  
}
main().then(() => process.exit(0)).catch(error => {
  console.error(error);
  process.exit(1);
});

I see, but why not just use the vanilla NFT contract, and call the native safeTransferFrom function, instead of defining your own transfer function?

- const result = await contract.transfer(WALLET_ADDRESS,RECIEVER_ADDRESS, TOKENID);
+ const result = await contract.safeTransferFrom(WALLET_ADDRESS, RECIEVER_ADDRESS, TOKENID);

Is that even possible? I didn’t know about it. How about the approval thing?

The approval is required only if the account that sends the tx is not the WALLET_ADDRESS, otherwise it just works!

It actually worked and I got this response.

{ hash:
   '0xf6e13fe498ebf333ea9be9d1aeea7163d4722a1bca201f9b1c91d0d0372182b1',
  type: 0,
  accessList: null,
  blockHash: null,
  blockNumber: null,
  transactionIndex: null,
  confirmations: 0,
  from: '0xB705730CDdDb2Af877A92B8D21bee30A61C0a416',
  gasPrice: BigNumber { _hex: '0x01dcd65000', _isBigNumber: true },
  gasLimit: BigNumber { _hex: '0x1127b8', _isBigNumber: true },
  to: '0x4c70949868D3F28BfCE917e161662C2ed901652b',
  value: BigNumber { _hex: '0x00', _isBigNumber: true },
  nonce: 45,
  data:
   '0x23b872dd000000000000000000000000b705730cdddb2af877a92b8d21bee30a61c0a416000000000000000000000000cc3677ae33d235b21d7388490aa07d4fb98ad0d30000000000000000000000000000000000000000000000000000000000000001',
  r:
   '0x5eea9093b529713920eb70c30e5f02f8c03e31d3b2c049c9b510a765b864f0ef',
  s:
   '0x77ab09a3202a36f32ce1968127d4b298eca196cd1cef7963315610046cdffa1e',
  v: 160037,
  creates: null,
  chainId: 80001,
  wait: [Function] }

The only problem though, none of the transfer transactions is getting approved on the blockchain and when I run the ownerOf(token_id) I get the same old address.

I get this error.

Error: (Awaiting internal transactions for reason)

You can check the live transaction here.
https://explorer-mumbai.maticvigil.com/tx/0xf6e13fe498ebf333ea9be9d1aeea7163d4722a1bca201f9b1c91d0d0372182b1/internal-transactions

I am just not sure if there is something wrong at my code or blockchain is not approving it for some reason. Have you faced anything like this in your development time?

I basically have a simple nft market place contract. I have the receiver implemented and basically call a safetransferfrom from within the contract to send the nft to itself from user. All goes well. Tested it and the receiver works well.

Now im scratching my head a bit with sending the nft from contract to buyer. The functionality is all fine, what im using though is TransferFrom. Here is the code:

  function Sale(
    address contract,
    uint256 itemId
    ) public payable nonReentrant {
    uint price = idToMarketItem[itemId].price;
    uint tokenId = idToMarketItem[itemId].tokenId;
    require(msg.value == price, "Please submit the asking price in order to complete the purchase");

    idToMarketItem[itemId].seller.transfer(msg.value);
    IERC721(contract).transferFrom(address(this), msg.sender, tokenId);
.................

Now what my worry is, if i change this to safeTransferFrom, and lets say it will be reverted, will the whole function just revert to not having happened? Or did it still send the value transfer but just reverted the nft transfer?