0

I am trying to implement a staking contract, I wanted to map all the owned NFTs by a certain address, so came across ERC721Enumerable and getTokenIdsOfOwnerfunction, however whenever I call my function I get Error: Transaction reverted without a reason string and I don't know how to debug that.

Here is my function

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

Thought I initialized the ERC721Enumerable mainNft wrongly but no the mainNft.balanceOf(_owner) works because I call it in this function and I get the result

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

1 Answer 1

0

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;
    }
}

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")
5
  • Its a contract provided by on of those minting services providers, they use proxy contracts and I can't get the hang of the original contract, any other way to achieve what I want that works regardless? Commented Apr 14 at 17:02
  • I've added a JS implementation for the same, in my answer. Please check. Commented Apr 14 at 17:30
  • 1
    Oh so I need to handle it JS cannot be done internally within the contract, okay thank you a lot! Commented Apr 14 at 17:47
  • Yes, it can’t be done internally using Solidity in this case. Commented Apr 14 at 18:02
  • Kindly please mark the answer as accepted, if that works for you. Thanks. Commented Apr 14 at 18:03

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.