Metaplex recently added on-chain collections to its metadata standard to allow objective identifying of NFT collections instead of various subjective and potentially conflicting heuristics employed by the community in absence of an on-chain standard.

The specification design makes it very easy to look up any given NFT and determine if it is in a collection and if so, which collection, by simply reading the Collection fields from the metadata account. The on-chain Metadata struct contains an option Collection struct which has a key field which is the Pubkey of the SPL token mint of the collection it belongs to.

pub struct Metadata {
	pub key: Key,
	pub update_authority: Pubkey,
	pub mint: Pubkey,
	pub data: Data,
	// Immutable, once flipped, all sales of this metadata are considered secondary.
	pub primary_sale_happened: bool,
	// Whether or not the data struct is mutable, default is not
	pub is_mutable: bool,
	/// nonce for easy calculation of editions, if present
	pub edition_nonce: Option<u8>,
	/// Token Standard is deterministic and will change from SemiFungible to NonFungible if
	/// you call the create master edition call and it succeeds.
	pub token_standard: Option<TokenStandard>,
	/// Since we cannot easily change Metadata, we add the new DataV2 fields here at the end.
	/// Collection
	pub collection: Option<Collection>,
...
}

#[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug, Clone)]
pub struct Collection {
	pub verified: bool, // Whether or not the collection is verified
	pub key: Pubkey,    // The SPL token mint account of the collection NFT
}

However, given a collection mint address, finding all NFTs that belong to that particular collection is significantly more difficult. There are two basic approaches to this which are outlined below.

GetProgramAccounts w/ PreCalculated Offsets

It’s tempting to think we can simply use a getProgramAccounts call with an offset into the Collection struct to match the collection id against the key field. This is the same way that most client programs find, e.g. a snapshot of NFT mint accounts belonging to a specific candy machine or creator ID. However, due to the fact that edition_nonce, token_standard, and collection are all Rust Options, this gets significantly more complicated.

Rust Options are represented in Borsh encoding with a 0 for the None variant and a 1 for the Some variant along with the normal encoding for whatever the contained value of the Some variant is. (One byte for a u8, for example.) This means that we can’t calculate an offset into the Collection struct without knowing what variant type is in each two of the two Options prior to the Collection field.

There are two ways to do this: brute force and gathering a priori knowledge of the variants.

Brute force requires computing all the possible variants and running multiple getProgramAccount calls in parallel. Given up to five creators in the creators array and two possible options for each of the two Option fields prior to Collection, that leads to a total number of 20 possible combinations, meaning that you would have to make 20 getProgramAccount calls with various offsets to take this approach. This is obviously not a feasible nor scaleable approach. If some a priori information is known about a collection though, this can be reduced to a smaller number of calls. Knowing how many creators there are is the biggest gain, reducing the number of getProgramAccount calls down to only four which can be reasonably run in parallel.

This approach is not the recommended one due to the high number of edge cases it involves and the fact that it can only be pragmatically used on collections where there is only one creator or the number of variations of how many creators there are is known ahead of time.

Instead, we recommend using the transaction crawling approach.

Transaction Crawling

Transaction crawling involves getting all the transactions associated with the collection mint address and then parsing them to find the specific instructions that create collections. From there we can determine which mint accounts are part of the collection.

The algorithm for doing this is shown below: