Why doesn't safeTransferFrom(from, _to, _tokenId, "") work with my ERC721 tokens on the testnets.opensea.io?

In my solidity code, I am able to mint new ERC721 tokens and successfully use the:

safeTransferFrom(from, _to, _tokenId, “”);

function to transfer these newly minted tokens between two accounts within my Metamask!

However I am NOT able to use the

safeTransferFrom(from, _to, _tokenId, “”);

code, to transfer MY ERC721 tokens that are viewable in my testnets.opensea.io account.

Kindly help me understand how I can enable this to happen…

Find below my Remix settings:
1

Find below my Solidity code:

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.2;

import “https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.4.0/contracts/token/ERC721/ERC721.sol”;

contract CryptoGift is ERC721 {

address public owner;

 constructor()  ERC721("Crypto Gift", "CGT") public {
    owner = msg.sender;
}

function createNFT(uint256 _tokenId) public {
    address NFTOwner = msg.sender;       
    _mint(NFTOwner, _tokenId);
}

function GiftAFriend ( address _to, uint256 _tokenId) public virtual {
    address from = msg.sender;
    uint256 numberOfTokens = balanceOf(from);
    require(numberOfTokens >= 1, "You dont haven any Tokens!");        
    
    safeTransferFrom(from, _to, _tokenId, ""); 
}

}

As you noticed in the image above, my MetaMask wallet address is: 0x358Bc77A023FD62Db821F40F7B4A9D3CBDadEfd3

in MetaMask I also have an alternative address which is:
0x4Ee7d298487B15ff2046A893f3364D8844C935e5

After deployment in Remix, and after minting a ERC721 token, I am able to successfully transfer the token to my alternative address:
0x4Ee7d298487B15ff2046A893f3364D8844C935e5 as illustrated below
1

The success also reflects in etherscan: https://rinkeby.etherscan.io/tx/0x02651ea221778fcc572bf0228b9872e1b9e0a24b92de4af250b5167df2e52900

I then switch addresses in my Metamask to my alternative address: 0x4Ee7d298487B15ff2046A893f3364D8844C935e5 with the intention of returning the token back to my original address, as illustrated below.
1
the success is also captured in etherscan at:
https://rinkeby.etherscan.io/tx/0xedd74b70c4ad675df4014c4a81ab518f792f5993417bcc71e7a56fbe67fcef62

This means my solidity code above works perfectly :ok_hand:t5: :ok_hand:t5: :ok_hand:t5:

The issue is when I try transferring the ERC721 tokens found in my testnets.opensea.io account, found at: https://testnets.opensea.io/assets/0x1a2795bbdbc6b6fc1d777b47b9c1516a642ca7f7/2

I get the below error message suggesting that the token doesn’t exist

Where am I going wrong, and how can I correct this?

what you mean by a token that exists in your opensea testnet account?
what is the connection between that token and that smart contract that you have?

My Rinkeby wallet address is:
0x358Bc77A023FD62Db821F40F7B4A9D3CBDadEfd3

When I run the owner function I get my wallet address
1

When I run the totalSupply function in Remix, it only shows me the number of ERC721 tokens that I minted via this contract, which are 3 tokens as seen below.
1

It does NOT include the ones that are on the testnets.opensea.io page. if it did, running the totalSupply function would show a total of 6, as I have 3 that are in my testnets.opensea.io account, also under wallet.

Why doesn’t totalSupply show me ALL the ERC721 tokens owned by wallet address 0x358Bc77A023FD62Db821F40F7B4A9D3CBDadEfd3?

Bare with me as I am new to the Blockchain development space, but isn’t that the way this is supposed to work?

The aim of my smart contract is NOT to mint ERC721 tokens at all, but rather to enable a user to give away their ERC721 tokens as gifts to anyone of their choosing, using the GiftAFriend function.

For this I was hoping a user would be able to copy and paste their tokenIds from their opensea account or even Metamask and feed it to the smart contract via a Dapp and away it goes.
How do I accomplish this?

I hope the above brought some clarity?

some opensea tokens are not even on chain (lazy minted tokens), usually a token corresponds to a contract address, and you have to call the function on the contract that mints the tokens, you can have tokens in multiple contracts

1 Like

Okay :astonished:… That explains alot!

Thank you for explaining this!

Now that you have made this very clear is there a universal function in the Solidity language that enables the capture of all tokens owned by A wallet, regardless of the number of tokens/contracts?

In other words, how do I get totalSupply to reflect all ERC721 tokens owned by a wallet, regardless if they are on the Rinkeby testsnet (lazy minted tokens) or on the mainnet?

Hi @sirBT, @cryptokid!

did you ever find a solution to the above?
I am facing the exact same issue (different use case) where I am retrieving ids of tokens through Moralis from opensea testnet (Rinkeby) accounts and even after getting approved:

  const approvalTransaction = await contract.setApprovalForAll(
    myDeployedMarketplaceAddress,
    true
  );

whenever I perform a safetransferFrom using the opensea NFT ids:

  const transaction = await contract[
    "safeTransferFrom(address,address,uint256)"
  ](myDeployedMarketplaceAddress, transferToAnotherAddress, tokenIdFromOpensea);

I keep getting (as you do):

operator query for non-existent token 

I know I am the Owner, but I keep getting told that the id does not belong in my smartcontract (which is true as it was minted in Opensea’s smartcontract) but isn’t setApprovalForAll supposed to perform exactly that? i.e. allow me to transfer tokens from one contract to another?

Curious to see if and how you solved this?

Note:
Just to be clear, these NFTs have been lazily minted on opensea, but they have then been transferred to another account I control, effectively making them belong on the Rinkeby Testnet.

@sirBT, to the best of my understanding, if a token is lazily minted, it does not exist on the blockchain and therefore cannot be retrieved, it very likely exists in a database (Opensea’s in this case) until it is issue onto the blockchain when it is bought (which is how openSea market their NFTs as free to make… the cost of deploying the NFT is captured by the purchaser)

setApprovalForAll supposed to perform exactly that? i.e. allow me to transfer tokens from one contract to another?

safeTransferFrom needs to be called on the original NFT contract, not yours or the address where ownership of them was transferred to. When you use setApprovalForAll, you are just giving permission to an address to transfer the tokens on the contract, and safeTransferFrom just changes the ownership - it doesn’t mean they now exist in your own contract.

You can read about setApprovalForAll here and safeTransferFrom here.

1 Like

@alex thank you so much for this,
In relation to calling safeTransferFrom from the Original Opensea contract, would I need to therefore be doing something like this:

const openseaContract = new ethers.Contract(
tokenAddressOnOpensea, 
OpenseaContract.abi,
signer);

if yes, as I already have the token’s address on the Opensea Smartcontract, where would I find OpenseaContract.abi to create the openSeaContract object?

Which, I believe, I would then use like this:

openseaContract.safeTransferFrom(msg.sender,newAddress, tokenId)

Note:
Whenever I try doing this specifically for Opensea, I do not seem to find the contract ABI anywhere. I am following this link but cannot find the ABI for this address

It may be similar enough to the one here.

Otherwise you can try using the partial ABI section for safeTransferFrom from this contract.

1 Like

You are correct, and I have successfully been able to use safeTransferFrom, so thank you for that. :slight_smile:

However, it feels a bit painful to have to store the ABI for every smart-contract address you have to interact with. Is there a better way to interact with another deployed smart-contract’s functions if one only has the contract address? is that something Moralis offer as an API?

However, it feels a bit painful to have to store the ABI for every smart-contract address you have to interact with

It’s just the way it is since the ABI is required for contract calls to work. Moralis API has runContractFunction to make it easier to call read only functions but it still requires an ABI.

Chain explorers like Etherscan have API endpoints for accessing ABIs for contract that have been verified so there’s certainly ways to make it easier.

Also if you’re interacting with contracts using common functions that come with the standard e.g. ERC20, ERC721 or contracts from OpenZeppelin, you’d be able to use the same partial ABI for those functions - this would include safeTransferFrom and setApprovalForAll.

1 Like