[Ethereum] ERC721 how does tokenOfOwnerByIndex() list tokens owned by a user


Per eip-721, there's an optional "enumeration extension". So what I did was I called balanceOf to get the number of tokens owned by the account and called tokenOfOwnerByIndex in a loop to get each owned token ID.

for(var i = 0; i < balance.toNumber(); i++) {
  myERC721Token.tokenOfOwnerByIndex.call(web3.eth.accounts[0], i)
  .then((id) => { ... });       

As a result, it actually listed tokens owned by a user.

However, I am not sure how the code actually works because tokenOfOwnerByIndex returns mapping variable ownedTokens, but I've never assigned any value to ownedTokens! But how can it still return the tokens of an owner? FYI, I mint the token like below.

import "openzeppelin-solidity/contracts/token/ERC721/ERC721Token.sol";

contract MyERC721Token is ERC721Token {

  function createNewToken(...) {
    _mint(msg.sender, index);

If you take a look at openzeppelin's contract, _mint that I called here never assigns any value to ownedTokens. Can anyone see the relationships between the contracts that I can find out why tokenOfOwnerByIndex is working for me?

Best Answer

ERC721Token inherits from ERC721BasicToken.

When a new token is minted, _mint is called from ERC721Token, which then calls super._mint which is the _mint implementation from ERC721BasicToken.

super._mint calls addTokenTo from the top level implementation (ERC721Token).

addTokenTo does additional logic to update the ownedTokens array in addition to calling super.addTokenTo, which again references the ERC721Basic token.

Thus, by using the ERC721Token you get this enumerable set of tokens for a user.

In general, you can think of this structure as one contract layer adding additional logic to another layer.

At the bottom you have ERC721BasicToken which by itself implements all of the functions needed for ERC721. Then you have ERC721Token which sits on top of ERC721BasicToken, and overwrites most of the functions with its own logic.

Cleverly, it ALSO includes the old logic using super.functionName syntax so that the old logic is run in addition to any new logic. Thus rather than changing the logic of the function, it extends the logic.

In this situation, the new logic does all the heavy lifting to make the tokens per owner enumerable.

Related Topic