22

It is possible to check which address owns a particular NFT by using the ERC721 standard function:

function ownerOf(uint256 tokenId) public view virtual override returns (address) in ERC721.sol

But I would like to know all NFTs owned by a particular address.

My Question:

Is there a way to retrieve all ERC721 tokens owned by a partcular Ethereum address using Web3.JS?

3
  • 2
    You check all Transfer events and filter it by receiving address. May 3, 2021 at 20:52
  • 1
    Is there a better way than that? What if the address received something and then sent it somewhere else later? May 3, 2021 at 20:57
  • 2
    You check also outgoing transfer events. May 4, 2021 at 9:48

7 Answers 7

28

In order to get all NFTs of a user, you need to have an indexed database where you save this data. Then you index all Transfer events of ERC721 contracts and eventually calculate the balances for every address and save this into a database where it's quickly accessible for every request. The transfer events to index for ERC721 is the following:

Transfer(address,address,uint256)

It's a pretty cumbersome process, and unfortunately, nothing that is available with a simple web3.js call. This has to do with the way data is structured in the blockchain, which just contains transactions and not actual indexed user data.

If you're looking for an easy way to get NFT balances of users with a simple call, you can use the service I built, moralis.io. We built it because I had the exact same issue you have. It's completely free to use.

  • /{WALLET_ADDRESS]/nft - get all NFTs owned by address
  • /nft/{CONTRACT_ADDRESS}/owners - get all owners of a specific NFT contract
  • /nft/{CONTRACT_ADDRESS}/{TOKEN_ID}/owners - get all owners of a specific NFT contract and ID

Or you can use the moralis sdk and you can use the frontend JavaScript functions.

//get NFTs for current user on ETH Mainnet
const userEthNFTs = await Moralis.Web3API.account.getNFTs();

//get all owners of specific NFTS
const options = { address: "0xd...07", chain: "bsc" };
const nftOwners = await Moralis.Web3API.token.getNFTOwners(options);

Full disclosure, I work at moralis.

4
  • How can you understand that a Transfer event is an ERC721 transfer? Feb 9 at 18:40
  • @SashaShpota In order to decode the logs associated with a certain transaction, which will also include the emitted Transfer event, you will need to use an ABI. That ABI specifies the expected structure for the Transfer event you are looking for, which differs for ERC20, ERC1155, and ERC721 transfer events. What seems to work, if you are just looking for ERC721 transfer events, is to only provide your decoder with that ABI. As such, it won't be able to read the transfers for the other types of tokens, and you'll know they aren't ERC721. Hope that helps! Feb 14 at 7:21
  • But some NFT is missing when I use the API by moralis.io. I tried to sync the NFT contract for indexing, but sill several NFT are missing for some EOA. Very hard to figure out what is wrong with it. No support at all for free plan user.
    – Kronos
    Mar 22 at 12:03
  • You can get help in the Moralis discord or forum. Usually when NFTs are missing it either doesn't adhere to any common ERC standard, or the metadata wasn't available to be fetched. But I'm sure the Moralis team can help you debug.
    – Filip
    Mar 23 at 13:04
10

Assuming opensea is aware of the tokens you could use their api also:

https://api.opensea.io/api/v1/assets?owner=${address}
6

No, web3 doesn't know about all ERC721 token contracts, which would be required to call ownerOf() for each NFT.

What could be done: take a list of popular ERC721 contracts (like this one from Bloxy) and check the owner of each and every token, which is quite cumbersome.

Alternative, more convenient approaches would be to use third-party APIs like Alchemy's Transfer API or Etherscan's Account API (see 'Get a list of "ERC721 - Token Transfer Events" by Address').

5

One thing worth checking out is if the NFT is ERC721Enumerable. This allows you to call the tokenOfOwnerByIndex function, which gives you to index through all the NFTs from the contract that the wallet has.

2
  • practically this is right answer. most contracts will be ERC721Enumerable and thus have tokenOfOwnerByIndex
    – user566245
    Mar 12 at 23:45
  • idk about most, I almost never use ERC721Enumerable unless there is explicit need, it's a gas guzzler
    – ihor.eth
    May 24 at 18:17
1

Fetching all ERC721 compliant NFTs is super easy to do with the Alchemy SDK and NFT API:

npm install @alch/alchemy-sdk

and then in your code:

// Initializing the alchemy config object
import { initializeAlchemy, getNftsForOwner } from '@alch/alchemy-sdk';

const alchemy = initializeAlchemy(); // using default settings - pass in a settings object to specify your API key and network

getNftsForOwner(alchemy, '0xshah.eth').then(console.log);
0

It is possible reading the events from the contract. This is an example using web3.js. You get the tokens that the address received and count the times it received each one. Then you count those who were sent by the address with the same procedure. If a token has been received more times than it was sent (it can only be equal or 1 more), then it means that it owns the token.

// Get the tokens that the account received
  const eventsReceivedTokens = await contract.getPastEvents("Transfer", {
    filter: {
      to: account,
    },
    fromBlock: 0,
  });

  // Count the number of times the account received the token
  let receivedTokensCount = {};
  for (let key in eventsReceivedTokens) {
    let tokenId = eventsReceivedTokens[key]["returnValues"]["tokenId"];
    receivedTokensCount[tokenId] = (receivedTokensCount[tokenId] || 0) + 1;
  }

  let receivedTokenIds = Object.keys(receivedTokensCount);

  // Get the tokens that the account sent
  const eventsSentTokens = await contract.getPastEvents("Transfer", {
    filter: {
      from: account,
      tokenId: receivedTokenIds,
    },
    fromBlock: 0,
  });

  let sentTokensCount = {};
  for (let key in eventsSentTokens) {
    let tokenId = eventsSentTokens[key]["returnValues"]["tokenId"];
    sentTokensCount[tokenId] = (sentTokensCount[tokenId] || 0) + 1;
  }

  // Substract the tokens received by the sent to get the tokens owned by account
  // Store them on ownedTokenIds
  let ownedTokenIds = [];
  for (let tokenId in receivedTokensCount) {
    if (
      (sentTokensCount[tokenId] ? sentTokensCount[tokenId] : 0) <
      receivedTokensCount[tokenId]
    ) {
      ownedTokenIds.push(tokenId);
    }
  }
0

It depends on the contract implementation. How the developer keeps track of the owner: This is one way you can implement it:

mapping(address=>mapping(uint=>uint)) private _ownedTokens;

this mapping is like:

{"0x32j23h32h423.....":{1:tokenId-1 , 2:tokenId-2}}

Have another mapping for a fast lookup:

 // id => index
 mapping(uint=> uint) private _idToOwnedIndex;

Then define a function to update the state of owner:

function _addTokenToOwner(address to, uint tokenId) private{
    uint length=ERC721.balanceOf(to);
    _ownedTokens[to][length]=tokenId;
    _idToOwnedIndex[tokenId]=length;
}

call _addTokenOwner() in _beforeTokenTransfer()

function _beforeTokenTransfer(address from, address to,uint tokenId)internal virtual override{
    // in case this is provided in super contract, the contract that you are extending from call this
    super._beforeTokenTransfer(from,to,tokenId);
    // that means we are minting the token
    // Becasue inside ERC721._mint we call _beforeTokenTransfer(address(0),to,tokenId)
    if(from==address(0)){
      _addTokenToAllTokens(tokenId);
    } 
    // this also satisfied when we mint the token. 
    if (to!=from){
      _addTokenToOwner(to, tokenId);
    }
}

Now write the function to get the NFTs that msg.sender owns. I don't think you should implement a way to view other account's NFTs.

function getOwnedNfts() public view returns(NftItem[] memory){
    uint ownedItemsCount=ERC721.balanceOf(msg.sender);
    NftItem[] memory items= new NftItem[](ownedItemsCount);
    for (uint i=0; i<ownedItemsCount; i++){
      uint tokenId=tokenOfOwnerByIndex(msg.sender, i);
      NftItem storage item=_idToNftItem[tokenId];
      items[i]=item;
    }
    return items;
}

This is the helper function used in the getOwnedNfts() function:

function tokenOfOwnerByIndex(address owner,uint index) public view returns(uint){
    require(index<ERC721.balanceOf(owner), "Index out of bounds");
    return _ownedTokens[owner][index];
}

Not the answer you're looking for? Browse other questions tagged or ask your own question.