Skip to main content

Ethereum NFT Collection API: Token List, Holders and Transfers

An NFT collection is one contract, and two Bitquery cubes describe it. Transfers on the archive dataset holds every mint and transfer of every token id since the contract was deployed, which gives you the full token list with each token's metadata URI. Holders holds the current owners, ranked by how many tokens of the collection each address holds, without you aggregating transfers yourself. Both examples use the Bored Ape Yacht Club contract 0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d; put any ERC-721 or ERC-1155 contract in its place. Every query runs in the IDE on a free account.

All token ids in a collection

Group the collection's transfers by token id on the archive dataset. Each row is one token, count is how many times it has moved, and offset pages through the whole collection in id order. For the current owner of one token, take its latest transfer with Id in the filter: the Receiver is the owner. Saved query here.

{
EVM(dataset: archive, network: eth) {
Transfers(
where: {Transfer: {Currency: {SmartContract: {is: "0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d"}}}}
limit: {count: 1000, offset: 0}
orderBy: {ascendingByField: "Transfer_Id"}
) {
Transfer {
Id
}
count
}
}
}

Holders of a collection, ranked

The Holders cube returns current balances per address for the collection contract, so the top rows are the largest holders. Add date: "YYYY-MM-DD" to see the holder set on a past day.

{
EVM(network: eth) {
Holders(
limit: { count: 100 }
orderBy: { descending: Balance_Amount }
where: { Currency: { SmartContract: { is: "0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d" } } }
) {
Holder {
Address
}
Balance {
Amount
}
}
}
}

The older way, summing BalanceUpdates per address, is retired; the Balances and Holders cubes page maps each old query to its replacement.

Transfer history and mints

Plain rows come from the same filter without the grouping. Add the zero address as Sender to keep only mints, Id for one token, or a Block.Time window for a period; the example lists the first BAYC mints from the collection's launch weeks on the archive dataset. Change query to subscription and drop the dataset argument to stream new transfers of the collection as they happen.

{
EVM(dataset: archive, network: eth) {
Transfers(
where: {
Transfer: {
Currency: { SmartContract: { is: "0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d" } }
Sender: { is: "0x0000000000000000000000000000000000000000" }
}
Block: { Time: { since: "2021-04-01T00:00:00Z", till: "2021-05-31T00:00:00Z" } }
}
limit: { count: 20 }
orderBy: { ascending: Block_Time }
) {
Block {
Time
}
Transfer {
Id
Receiver
URI
}
Transaction {
Hash
}
}
}
}

Frequently Asked Questions

How do I list every NFT in a collection?

Group Transfers on the archive dataset by Transfer.Id with count, filtered by the collection contract, and page with limit and offset; each row is one token id. Plain transfer rows carry the URI for metadata.

How do I get the holders of an NFT collection on Ethereum?

Query the Holders cube with the collection contract in Currency.SmartContract and sort by Balance_Amount. It returns current owners with their token counts; a date argument returns the owners on a past day.

Does this work for ERC-1155 collections?

Yes. Transfers carry the token id and amount for ERC-1155 as well, and Holders returns per-address balances, which can be larger than one for the same id.

How do I get NFT metadata?

The URI field on a transfer row is the token's metadata link, and Data holds on-chain metadata when the contract stores it. Fetch the URI yourself for the image and attributes.

Can I do the same on other EVM chains?

Yes. Change EVM(network: eth) to bsc, base, arbitrum, optimism, matic or robinhood; the Transfers and Holders cubes have the same fields on every EVM chain.

Build with Bitquery

Ready to run this in production?

Get an API key and run these queries in minutes, or talk to us about plans and enterprise delivery.