6

I am using openzeppelin erc721 library to create an NFT and need to list all the tokens of a user. The most obvious way is to iterate through all the tokens in the contract and check if the owner of the token is same as the address I have sent.

function tokensOfOwner(address _owner) external view returns(uint256[] ownerTokens) {
    uint256 tokenCount = balanceOf(_owner);

    if (tokenCount == 0) {
        // Return an empty array
        return new uint256[](0);
    } else {
        uint256[] memory result = new uint256[](tokenCount);
        uint256 totalCats = totalSupply();
        uint256 resultIndex = 0;

        // We count on the fact that all cats have IDs starting at 1 and increasing
        // sequentially up to the totalCat count.
        uint256 catId;

        for (catId = 1; catId <= totalCats; catId++) {
            if (kittyIndexToOwner[catId] == _owner) {
                result[resultIndex] = catId;
                resultIndex++;
            }
        }

        return result;
    }
}

Link to this solution

Another solution that I am thinking of implementing is to have a mapping where I map the user address to an array or another mapping which includes all the tokens the user owns. Upon transferring the token to another address, I would need to remove the token from seller list and add it to buyers list. This solution sounds more reasonable but I dont know how cost efficient it will be. What are the disadvantages of this method?

2
  • 1
    Whatever you do, try to stay away from loops. They are consuming gas. Try to solve these kind of problems off-chain if you can. May 5 at 19:08
  • take look at this docs.openzeppelin.com/contracts/2.x/api/token/… and see how they implement this method _tokensOfOwner(owner) return all tokens of owner in ERC721 is not a good idea, it is a "NFT" and such a thing you trying to do will only work for a very low number of tokens
    – Majd TL
    May 7 at 13:06
3
+100

This can be achieved by the following:

Declare storage variables at the contract level

mapping(address => uint256[]) public userOwnedTokens;
mapping(uint256 => int256) public tokenIsAtIndex;

mint(tokenId) {
    // Prior minting logic from OpenZeppelin
    userOwnedTokens[msg.sender].push(tokenId);
    uint256 arrayLength = userOwnedTokens[msg.sender].length;
    tokenIsAtIndex[tokenId] = arrayLength;
}

To get all user tokens without loop, all you have to do is, this is possible as the access specifier for userOwnedTokens is public

contractInstance.methods.userOwnedTokens.call(address) // THIS WILL RETURN AN ARRAY OF TOKEN IDs

In case of transfer of tokens, change tokenIsAtIndex[tokenId] to -1

transfer(from, to, tokenId) {
    // Transfer logic
    uint256 tokenIndex = tokenIsAtIndex[tokenId];
    userOwnedTokens[from][tokenIndex] = 000; // TO DENOTE THAT THE TOKEN HAS BEEN TRANSFERRED, YOU CAN USE ANY OTHER NUMBER
 }

Next time when userOwnedTokens is called you can keep a check wherever the tokenId is 000, those tokens have already been transferred and you can decide not to show it to the user on the frontend.

I hope it helps!!!!!

0

As you’re already extending OpenZeppelin’s ERC721 contract, simply extend ERC721Enumerable too, and you may then enumerate user’s tokens by calling tokenOfOwnerByIndex(user, index) for index = 0, 1, 2, …, balanceOf(user) - 1.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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