Question it answers
“Give me every current owner of NFT collection 0x…: eachThis is the by-collection sibling of NFTs by Wallet: same ingest, sorted the other way. Each NFT transfer carries the absolute post-transfer holdings of both wallets, so the latest observation pertoken_id, the wallet that holds it, and how many (ERC-1155 editions).” Mirrors MoralisGET /nft/{address}/owners.
(token_address, token_id, wallet) is the current owner. Storing the resolved owner set in your own database, keyed by collection, is the value.
What you get
Each NFT transfer becomes two per-wallet holding observations (leg = from | to), where amount is the absolute count of that token_id held by the wallet after the transfer. The latest observation per (token_address, token_id, wallet_address) wins; keep amount > 0 for the current owner set.
Source
The transform reads one per-block array,nftTokenTransfers, and unpivots each event into two observations using its fromPostBalance / toPostBalance fields (the from- and to-wallet’s absolute count of that token_id after the transfer). No price or metadata join is involved.
Destination
ClickHouse uses the collapsing log-table pattern (see the recipes overview) so chain reorganizations self-correct. The fact table is sorted collection-first, so “every owner of collection X” is a contiguous range read. Ownership is latest-wins: an observation is current state for its holding key, not an append-only event. Read canonical state with
FINAL then argMax, never a bare WHERE sign = 1.
Full schema
Below is the complete read table this recipe produces: one row per holding observation. Keep the columns you need and drop the rest (see Schema & flexibility).token_id and amount are stored as text / wide numeric because full-range uint256 ids and counts overflow standard numeric types.
ClickHouse, fact_nft_owners_by_contract
ClickHouse, fact_nft_owners_by_contract
sign column drives reorg collapsing: read with FINAL then argMax, or a sign-aware aggregate. A single-node setup can use CollapsingMergeTree(sign) without the replication path.Postgres, nft_owner_observations + nft_owners
Postgres, nft_owner_observations + nft_owners
nft_owners state table and a DELETE … WHERE amount = 0 cleanup. position is the block-level cursor used during backfill. Refresh the materialized view on a schedule with REFRESH MATERIALIZED VIEW CONCURRENTLY nft_owners;.Example reads
Current owners of a collection, latest non-zero holding pertoken_id + wallet (ClickHouse):
Modes
Shipped defaults: ClickHousehybrid (backfill → realtime), Postgres / MySQL historical (one-shot backfill). For live/reorg-safe ingestion, use ClickHouse (see the overview).
Realtime/hybrid on Postgres / MySQL is constrained because the block-level
position cursor requires a single-column unique key. Run realtime/hybrid on ClickHouse; the Postgres / MySQL configs target historical backfill.Multichain
The recipe is chain-parametrized via thechain setting: point it at any supported EVM chain. NFTs on Solana are SPL tokens; if a chain emits NFT transfers under the same nftTokenTransfers struct, set the Solana chain variables. The vendor_event_id already incorporates (token_id, amount) on top of log_index to stay row-unique under Solana’s repeated log indices; the owner surface it produces is identical in shape.
Fidelity gaps
This recipe returns on-chain ownership only. Fields a Moralis/nft/{address}/owners response carries that have no onchain source (they need a separate metadata/indexer service) are not populated:
name,symbol,token_uri,metadata,normalized_metadata: collection / token metadata (use an NFT Collection Metadata recipe).possible_spam, verified-collection flags: risk / curation signals.block_number_mintedand historical mint provenance beyond the backfill window.
amount is the raw on-chain holding count, not a marketplace listing. The core access key (token_address → owners with token_id, amount, contract_type) is fully sourced from nftTokenTransfers.
Migrating from the REST API
This recipe replaces the by-collection ownership endpoints of the Web3 API. Data Feeds are not 1:1 replicas of those responses: you land the underlying data in your own database and reconstruct the response shape with a query. The subsections below cover each REST endpoint: what replaces it, the field mapping (exact = straight from the chain, calculated = derived from real trades, very close, add yourself = off-chain signal not in the feed), and the endpoint-specific gotchas.GET /nft/:address/owners
Every owner of every token in a collection. Replaced by this recipe’s owner surface (nft_owners on Postgres/MySQL, argMax over the ClickHouse fact table): one row per (token_id, owner) with amount > 0.
The reconstruction query (Postgres; adapt to your destination):
- Backfill from the collection’s deploy block. Ownership is current state (the cumulative result of every transfer), so a late start returns stale or missing owners. See History & backfill.
- ERC-1155 token_ids can have many owners, so you get a row per
(token_id, owner); ERC-721 has one owner per id. - Big collections paginate heavily; keyset on
(token_id, wallet_address)instead of offset pages.
GET /nft/:address
Every NFT in a collection with its current owner. Same recipe, same tables, same joins as/nft/:address/owners above: the two endpoints are one read ordered differently (ORDER BY o.token_id for the collection listing). The field mapping is identical, plus the listing’s extra enrichment fields: media, the collection editorial fields (logo, banner, category, social links), and floor_price* are all off-chain. Add yourself from your metadata resolver and marketplace sources, or drop them.
Gotchas
- The REST endpoint was mostly a metadata product; the chain only carries ownership, mint, and identity. If resolved JSON metadata and media are the entire job, keep a metadata indexer for that layer; this recipe supplies the ownership and provenance to pair with it.
- The REST
totalRanges/rangeparallel-scan pagination has no equivalent: it’s your table now; scan or keyset ontoken_id. - Backfill from the collection’s deploy block, as above.
Related
NFTs by Wallet
The same ingest, sorted by wallet: every NFT a wallet holds.
NFT Transfers
The event-level feed behind these owner observations.
NFT Collection Metadata
The name, symbol, and token URIs this recipe leaves out.
NFT Marketplace
The use case this owner surface powers.

