Skip to main content
Added the JS implementation for retrieving the token ids corresponding to simple ERC721 contracts.
Source Link
SYED ASAD KAZMI
  • 5.8k
  • 2
  • 7
  • 22

If the NFT contract is a simple ERC721 (i.e., not extending the ERC721Enumerable), then one way to get the token ids owned by an address is to fetch and filter the logs of the corresponding NFT contract.

Here's a minimal JS code (created by following this hardhat implementation) to achieve the same:

const { ethers } = require('ethers');
const provider = new ethers.JsonRpcProvider("JSON_RPC_URL");
const ERC721 = require('@openzeppelin/contracts/build/contracts/ERC721.json');

async function listTokensOfOwner(tokenAddress, account) {
    const token = new ethers.Contract(
        tokenAddress,
        ERC721.abi,
        provider,
    );

    const sentLogs = await token.queryFilter(
        token.filters.Transfer(account, null),
    );
    const receivedLogs = await token.queryFilter(
        token.filters.Transfer(null, account),
    );

    const logs = sentLogs.concat(receivedLogs)
        .sort(
            (a, b) =>
                a.blockNumber - b.blockNumber ||
                a.transactionIndex - b.transactionIndex,
        );

    const owned = new Set();

    for (const { args: { from, to, tokenId } } of logs) {
        if (addressEqual(to, account)) {
            owned.add(tokenId.toString());
        } else if (addressEqual(from, account)) {
            owned.delete(tokenId.toString());
        }
    }

    return owned;
};

function addressEqual(a, b) {
    return a.toLowerCase() === b.toLowerCase();
}

async function getTokenName(tokenAddress) {
    const token = new ethers.Contract(
        tokenAddress,
        ERC721.abi,
        provider,
    );

    return token.name();
}

async function main(token, account) {
    console.log(await getTokenName(token), 'tokens owned by', account);
    const owned = await listTokensOfOwner(token, account);
    console.log([...owned].join('\n'));
};

main("NFT_CONTRACT_ADDRESS", "WALLET_ADDRESS")

If the NFT contract is a simple ERC721 (i.e., not extending the ERC721Enumerable), then one way to get the token ids owned by an address is to fetch and filter the logs of the corresponding NFT contract.

Here's a minimal JS code (created by following this hardhat implementation) to achieve the same:

const { ethers } = require('ethers');
const provider = new ethers.JsonRpcProvider("JSON_RPC_URL");
const ERC721 = require('@openzeppelin/contracts/build/contracts/ERC721.json');

async function listTokensOfOwner(tokenAddress, account) {
    const token = new ethers.Contract(
        tokenAddress,
        ERC721.abi,
        provider,
    );

    const sentLogs = await token.queryFilter(
        token.filters.Transfer(account, null),
    );
    const receivedLogs = await token.queryFilter(
        token.filters.Transfer(null, account),
    );

    const logs = sentLogs.concat(receivedLogs)
        .sort(
            (a, b) =>
                a.blockNumber - b.blockNumber ||
                a.transactionIndex - b.transactionIndex,
        );

    const owned = new Set();

    for (const { args: { from, to, tokenId } } of logs) {
        if (addressEqual(to, account)) {
            owned.add(tokenId.toString());
        } else if (addressEqual(from, account)) {
            owned.delete(tokenId.toString());
        }
    }

    return owned;
};

function addressEqual(a, b) {
    return a.toLowerCase() === b.toLowerCase();
}

async function getTokenName(tokenAddress) {
    const token = new ethers.Contract(
        tokenAddress,
        ERC721.abi,
        provider,
    );

    return token.name();
}

async function main(token, account) {
    console.log(await getTokenName(token), 'tokens owned by', account);
    const owned = await listTokensOfOwner(token, account);
    console.log([...owned].join('\n'));
};

main("NFT_CONTRACT_ADDRESS", "WALLET_ADDRESS")
Source Link
SYED ASAD KAZMI
  • 5.8k
  • 2
  • 7
  • 22

It seems that the mainNft contract you're instantiating hasn't extended the ERC721Enumerable contract in its implementation.

It would work fine if the NFT contract being created inherits/extends the ERC721Enumerable contract like this:

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

import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import {IERC721Enumerable} from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol";
import {ERC721Enumerable} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";

contract MyNFT is ERC721Enumerable {
    constructor(string memory name, string memory symbol)
        ERC721(name, symbol)
    {}

    function mint(address to, uint256 tokenId) public {
        _mint(to, tokenId);
    }
}

contract MyNFTCount {
    IERC721Enumerable public mainNft;

    constructor(address myNFTAddress) {
        mainNft = IERC721Enumerable(myNFTAddress);
    }

    function viewOwnerNFTBalance(address _owner)
        public
        view
        returns (uint256 balanceOfOwner)
    {
        return mainNft.balanceOf(_owner);
    }

    function getTokenIdOfOwnerByIndex(address _owner, uint256 index)
        public
        view
        returns (uint256)
    {
        return mainNft.tokenOfOwnerByIndex(_owner, index);
    }

    function getTokenIdsOfOwner(address _owner)
        public
        view
        returns (uint256[] memory tokensOfOwner)
    {
        uint256 _len = mainNft.balanceOf(_owner);
        uint256[] memory _tokensOfOwner = new uint256[](_len);
        for (uint256 i = 0; i < _len; i++) {
            _tokensOfOwner[i] = mainNft.tokenOfOwnerByIndex(_owner, i);
        }
        return _tokensOfOwner;
    }
}