Let's say that I have an ERC721 (NFT) contract that represents a set of playing cards.
Is there sample code out there that shows listing all of the tokens of that ERC721 contract that a user owns on a web page so that the user could select one?
Ethereum Stack Exchange is a question and answer site for users of Ethereum, the decentralized application platform and smart contract enabled blockchain. It only takes a minute to sign up.
Sign up to join this communityLet's say that I have an ERC721 (NFT) contract that represents a set of playing cards.
Is there sample code out there that shows listing all of the tokens of that ERC721 contract that a user owns on a web page so that the user could select one?
You could add a function to your smart contract that mimics what the CryptoKitties contract does in function tokensOfOwner
, which
Returns a list of all Kitty IDs assigned to an address
/// @title The facet of the CryptoKitties core contract that manages ownership, ERC-721 (draft) compliant.
/// @author Axiom Zen (https://www.axiomzen.co)
/// @dev Ref: https://github.com/ethereum/EIPs/issues/721
/// See the KittyCore contract documentation to understand how the various contract facets are arranged.
contract KittyOwnership is KittyBase, ERC721 {
[...]
/// @notice Returns a list of all Kitty IDs assigned to an address.
/// @param _owner The owner whose Kitties we are interested in.
/// @dev This method MUST NEVER be called by smart contract code. First, it's fairly
/// expensive (it walks the entire Kitty array looking for cats belonging to owner),
/// but it also returns a dynamic array, which is only supported for web3 calls, and
/// not contract-to-contract calls.
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;
}
}
The current draft of the ERC721 standard calls for contracts adhering to that standard to have a totalSupply
attribute, as well as balanceOf(address _owner)
and ownerOf(uint256 _tokenId)
functions.
To find all items a given address owns, the front-end DApp would need to query for the totalSupply
(say, 52 playing cards), and then loop from zero to that number and call the ownerOf
method on that ID, looking for the result to match the target address being searched for. When a match is found, that item can be added to a list of known assets for that account, and if the length of that list is now equal to the result of a balanceOf
call for that user, the looping can stop since all assets have been found.
The ERC721 standard optionally calls for a tokenOfOwnerByIndex(address _owner, uint256 _index)
call that can make the looping shorter, but that is an optional function to implement for an ERC721 token. If the token you've created has such a function, then you can use that to loop through the results more efficiently.
tokenId
might not be consecutive numbers, and iterating over possible uint256 values is a no go. A better approach is to listen for Transfer
events.
from
== 0) and destroyed (to
== 0). Exception: during contract creation, any number of NFTs may be created and assigned without emitting Transfer. You are correct listen for Transfer
will work but not in all cases.
Transfer
event, how can you evaluate if a user is still the owner? The NFT could be transferred again at a later point.
Here's a code example created with React and Web3. Based on the details from Midnight Lightning.
for(var i = 0; i < totalSupply; i++) {
const nft = await contract.methods.YourSmartContract(i).call();
const owner = await contract.methods.ownerOf(i + 1).call();
// if match with this address is found
if(this.state.account === owner) {
this.setState({ nfts: [...this.state.nfts, nft] });
}
}