NEP-181: NFT enumeration
NEP-181 NFT enumeration standard on NEAR: nft_total_supply, nft_tokens, nft_supply_for_owner and nft_tokens_for_owner, and how to paginate them safely.
Intermediate5 min read3-question check
The NFT core (NEP-171) can look up one token by ID, but cannot answer “what does Alice own?” or “show me the whole collection”. NEP-181 adds four view methods for that, so wallets and marketplaces can list tokens straight from the contract without running an indexer.
Methods#
| Method | Kind | Returns |
|---|---|---|
nft_total_supply() | view | Number of tokens in the contract, as a string (U128). |
nft_tokens(from_index?, limit?) | view | A page of Token objects across the whole contract. |
nft_supply_for_owner(account_id) | view | How many tokens an account owns, as a string. "0" if none. |
nft_tokens_for_owner(account_id, from_index?, limit?) | view | A page of the account’s tokens. |
Paginating#
# how many?
near view nft.example.near nft_supply_for_owner '{"account_id": "alice.near"}'
# → "137"
# first page
near view nft.example.near nft_tokens_for_owner \
'{"account_id": "alice.near", "from_index": "0", "limit": 50}'
# next pages: advance from_index by the page size
near view nft.example.near nft_tokens_for_owner \
'{"account_id": "alice.near", "from_index": "50", "limit": 50}'- Use the supply call to know when to stop, or stop when a page returns fewer items than
limit. - View calls are free for the caller but still run under a gas limit on the RPC node. A huge
limit— especially with large on-chain metadata per token — can exceed it and fail. Pages of tens of tokens are a safe default. - Ordering is defined by the contract’s storage structure, not by mint time. If a token is transferred between your page requests, it can be skipped or seen twice; treat results as a snapshot.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
// ERC721Enumerable: one index at a time
interface IERC721Enumerable {
function totalSupply() external view returns (uint256);
function tokenByIndex(uint256 index) external view returns (uint256);
function tokenOfOwnerByIndex(address owner, uint256 index)
external view returns (uint256);
}use near_contract_standards::non_fungible_token::enumeration::NonFungibleTokenEnumeration;
use near_contract_standards::non_fungible_token::Token;
use near_sdk::json_types::U128;
use near_sdk::{near, AccountId};
// pages of full Token objects; delegate to the embedded NonFungibleToken
// (created with an Enumeration storage prefix)
#[near]
impl NonFungibleTokenEnumeration for Contract {
fn nft_total_supply(&self) -> U128 {
self.tokens.nft_total_supply()
}
fn nft_tokens(&self, from_index: Option<U128>, limit: Option<u64>) -> Vec<Token> {
self.tokens.nft_tokens(from_index, limit)
}
fn nft_supply_for_owner(&self, account_id: AccountId) -> U128 {
self.tokens.nft_supply_for_owner(account_id)
}
fn nft_tokens_for_owner(
&self,
account_id: AccountId,
from_index: Option<U128>,
limit: Option<u64>,
) -> Vec<Token> {
self.tokens.nft_tokens_for_owner(account_id, from_index, limit)
}
}tokenOfOwnerByIndex returns one ID per call; NEP-181 returns a page of full Token objects (owner, metadata, approvals) per call — far fewer round trips.
Enumeration costs extra storage (a per-owner token set). The reference NonFungibleToken only maintains it if you pass an enumeration prefix to NonFungibleToken::new.
Check yourself
3 questions · progress saved in this browser