Skip to content

Instantly share code, notes, and snippets.

@creativedrewy
Last active September 7, 2022 09:58
Embed
What would you like to do?
Steps for Metaplex NFT Display

So you want to display a Metaplex NFT

This guide will attempt to give an overview of the technical/coding steps that are required to render a Metaplex NFT with any programming language/platform. I'll attempt to write it in a programming language-agnostic manner; you'll need to fill in the particular methods of performing the steps with your coding language of choice.

For the purposes of discussion, we'll call the Solana account that holds the Metaplex NFTs the "NFT-Account."

Step 1: Call getTokenAccountsByOwner

The first thing you need to do is call the getTokenAccountsByOwner JSON RPC method as documented here:

https://docs.solana.com/developing/clients/jsonrpc-api#gettokenaccountsbyowner

The first parameter you'll pass is of course the NFT-Account address. You'll also need to pass a programId argument with the Solana Token Program ID, which is TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA. I also recommend the jsonParsed argument at this point.

Both the NFT-Account and Token program account need to be encoded as Base58.

Step 2: Locate NFT Accounts

From the list of accounts that you get back from the RPC call, you'll want to find NFT token accounts. For each account, you'll want to look in the tokenAmount data object/properties. You're looking for accounts with an amount of 1 and a decimals value of 0.

Step 3: Derive PDAs for the NFT accounts

The next step is to derive a Prorgram Derived Address for each NFT account address. PDAs are discussed here:

https://docs.solana.com/developing/programming-model/calling-between-programs#program-derived-addresses

The actual technical details of PDA generation is actually still something of a mystery even to me, so I can't explain the nitty gritty details on how or why they are generated. Suffice it to say that you need the PDA that is generated from the NFT token accounts to actually get the on-chain Metaplex NFT metadata. The best way to see how to generate a PDA is in the metaplex source, which you can find here:

https://github.com/metaplex-foundation/metaplex/blob/master/js/packages/common/src/actions/metadata.ts#L536

You'll need a few things to generate the PDA. The first is an array of "seeds," which includes:

  1. The Metaplex seed constant: metadata
  2. The metadata public key: metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s
  3. The NFT token account mint address, which is also returned from the RPC call.

With that seed data, you can perform the findProgramAddress operation and that will result in a PDA account/address. Both data items 2 and 3 need to be Base58 encoded.

Step 4: Call getAccountInfo for each PDA account

For each PDA account address, you then need to call the getAccountInfo JSON RPC method:

https://docs.solana.com/developing/clients/jsonrpc-api#getaccountinfo

Step 5: Borsh deserialize the Metaplex meta

For each account info object that you get back from the previous call, you will need to need to Borsh deserialize the data property in the resulting JSON. There are Borsh libraries for most programming languages which are listed here:

https://github.com/near/borsh

You will be deserializing the data property into a programming-language specific version of the Metadata object hierarchy. The Javascript version of that data is here:

https://github.com/metaplex-foundation/metaplex/blob/master/js/packages/common/src/actions/metadata.ts#L224

One of the child objects there, Data, has the specific property we are looking for: uri. That URI is the link to the Arweave JSON metadata file.

Step 6: Parse the NFT JSON data

As noted, the uri property points to an Arweave JSON file that is formatted based on a common NFT JSON standard. I believe it is ERC721/ERC1155 format. The format is documented in detail here:

https://docs.metaplex.com/nft-standard#json-structure

Step 7: Use the JSON data to display

That JSON is the final place for information about your NFT. All the actual user-relevant metadata is there, as well as links to the relevant asset file or files to display or render.

Step 8: Display your snazzy NFT!

I hope this was helpful! If you liked this guide, say hey on Twitter!

https://twitter.com/creativedrewy

@bhgames
Copy link

bhgames commented Sep 2, 2021

In step 3 and 4, when you call getAccountInfo and findProgramAddress, you need to call it on a specific program (token metadata). Probably mention that.

I'd also mention that if we want to be strict here, you also want to derive the edition PDA which is [metadata, metadata program, mint_id, 'edition'], and also get THAT account. If the account does not exist, the token you're looking at is just part of a normal mint (like USDC or COPE) that has decided to use token-metadata to name itself. To make it more clear, ANY token that does not have an edition account is NOT an NFT.

If that account does exist, and it's of the MasterEdition type, then you know it's an original and may or may not have printing capabilities. The Master Editions' max_supply field being null means it is unlimited and can have unlimited editions printed from it using the mint_edition_from_master_edition_via_token contract endpoint on token metadata contract. The max_supply field being set means it can only have that many children editions. If the account exists and it's of the Edition type, it is a child edition of some master edition and is either a Limited Edition (printed from a finite supply) or an Open Edition (printed from an infinite supply).

@creativedrewy
Copy link
Author

Thanks so much for the comments! Some follow up questions:

In step 3 and 4, when you call getAccountInfo and findProgramAddress, you need to call it on a specific program (token metadata). Probably mention that.

I'm not quite following what you mean here; is it a terminology consideration? In step 4, I'm calling it the "PDA account address," but is the "official" moniker for it the "token metadata program" - since it's the derived PDA?

I'd also mention that if we want to be strict here, you also want to derive the edition PDA which is [metadata, metadata program, mint_id, 'edition'], and also get THAT account

Is this absolutely required? Basically, if it's one of the "normal" mint tokens it just won't have valid Borsh data, correct? I've already encountered some supposed NFTs that don't have valid borsh data and I just silently fail on the deserialize. I figure there are any number of accounts that won't be set up right so trying/catching on the deserialize is the way to go.

If that account does exist, and it's of the MasterEdition type, then you know it's an original...

For this paragraph, is there anything in this regard that I need to watch out for on the render side? Assuming a user has done the previous step, of course. Would it be to display that a particular NFT is the master edition vs "printed"?

@bhgames
Copy link

bhgames commented Sep 2, 2021

Well when you derive a PDA account you have to derive it relative to some program, but you were not clear in your text which program you need to derive it relative to.

Regarding the edition, it is absolutely required. All NFTs in the protocol have two PDA structs: The Metadata AND either an Edition struct or a MasterEdition struct. The Edition and MasterEdition share the same derived key formulation so that there can't be any collision (ie a metadata that has both). The token metadata program is meant to be used by all mints, not just mints of NFTs, so it is intended for instance for USDC to have a metadata as well. USDC though would not have a Master Edition.

Yes, it would be nice from the user side to show if an NFT is a master edition or if it is a child edition (ie a print.) I feel like users should know that. The Edition struct also tells you what edition number it is (was it print #4, or #41?).

@creativedrewy
Copy link
Author

Regarding the edition, it is absolutely required.

Just one more point of clarification. In my app, I'm not doing this step at all and I'm able render NFTs. You can see the class here:

https://github.com/creativedrewy/NATIV/blob/main/SolanaNFT/src/main/java/com/creativedrewy/solananft/metaplex/MetaplexNftUseCase.kt

Again, if the Borsh data doesn't match the Metaplex metadata (deserialized line 48), it just won't deserialize and be added to the list of rendered NFTs. Apart from the master/print edition details, what is this functionality missing out on by doing things this way?

@bhgames
Copy link

bhgames commented Sep 3, 2021

It just means I could fool you. I could create an "NFT" with 1000 tokens and your thing would still think it was an NFT. I could just send 1 token to 1000 accounts. You'd think each one was an NFT, but it isnt.

@creativedrewy
Copy link
Author

Duly noted, thanks for all the feedback!

@vicyyn
Copy link

vicyyn commented Sep 13, 2021

I've been stuck with the deserialization part in js using borsh. is it possible to get the common library in node ?

@sdbarrington
Copy link

anyone got a node/python working example of this? I'm struggling to get it all working

@vicyyn
Copy link

vicyyn commented Oct 4, 2021

anyone got a node/python working example of this? I'm struggling to get it all working

https://github.com/vicyyn/MetaplexMetadata-js

@sdbarrington
Copy link

anyone got a node/python working example of this? I'm struggling to get it all working

https://github.com/vicyyn/MetaplexMetadata-js

Thanks!

@tonyfa
Copy link

tonyfa commented Oct 8, 2021

Hello all

I am trying to get all NFT tokens addresses for a collection.

For example

I have a one "Skeleton Crew Skulls" NFT token address as follows:
https://solscan.io/token/3mR339tMvJ2Si8ZL62NZLbaEu1ukEdKxtFfMSPoQFfxH

I can see "Skeleton Crew Skulls" Update Authority is
Sku1HPHt6DE5Qz9q6zFV4MGNPxEJB8sbqXYdPou5PKG

From that how do I get the entire list of 6,666 "Skeleton Crew Skulls" addresses?

I tried the following but it is not working
{
"jsonrpc": "2.0",
"id": 1,
"method": "getTokenAccountsByOwner",
"params": [
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
{
"mint": "Sku1HPHt6DE5Qz9q6zFV4MGNPxEJB8sbqXYdPou5PKG"
},
{
"encoding": "jsonParsed"
}
]
}

Thanks to anyone who can answer this in advance.

@roadbuilder
Copy link

Hello all

I am trying to get all NFT tokens addresses for a collection.

get the verified creator of that token including the index of the creator and then run this, it will give you the whole list: (python code)

import requests
import jsonimport requests
import json

# url="https://api.mainnet-beta.solana.com"
url="https://solana-api.projectserum.com"

# url="https://api.mainnet-beta.solana.com"
url="https://solana-api.projectserum.com"
METADATA_PROGRAM_ID="metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"
MAX_NAME_LENGTH = 32;
MAX_URI_LENGTH = 200;
MAX_SYMBOL_LENGTH = 10;
MAX_CREATOR_LEN = 32 + 1 + 1;

creatorIndex=0
creatorAddress="Bhr9iWx7vAZ4JDD5DVSdHxQLqG9RvCLCSXvu6yC4TF6c"

payload=json.dumps({
    "jsonrpc":"2.0",
    "id":1,
    "method":"getProgramAccounts",
    "params":[
        METADATA_PROGRAM_ID,
        {
            "encoding":"jsonParsed",
            "filters":[
                {
                    "memcmp": {
                        "offset":
                            1 + # key
                            32 + # update auth
                            32 + # mint
                            4 + # name string length
                            MAX_NAME_LENGTH + # name
                            4 + # uri string length
                            MAX_URI_LENGTH + # uri
                            4 + # symbol string length
                            MAX_SYMBOL_LENGTH + # symbol
                            2 + # seller fee basis points
                            1 + # whether or not there is a creators vec
                            4 + # creators vec length
                            creatorIndex * MAX_CREATOR_LEN,
                        "bytes": creatorAddress
                    },
                },
            ],            
        }
    ]
})

headers={"content-type":"application/json","cache-control":"no-cache"}
response=requests.request("POST",url,data=payload,headers=headers)
data=response.json()["result"]
print("total records:",len(data))


@Cinderella941217
Copy link

anyone got a node/python working example of this? I'm struggling to get it all working

https://github.com/vicyyn/MetaplexMetadata-js

Thanks for your kind share. But I faced one problem with this example.
When I try to decode index.ts file, I got this error. could u please let me know the reason? How can i fix it?
image

@tttimur
Copy link

tttimur commented Nov 10, 2021

@Cinderella941217 make sure you're using ts-node, replace node-fetch with isomorphic-fetch and replace "require('b58')" with "import b58"

@ilmoi
Copy link

ilmoi commented Nov 15, 2021

Sooooooo I started with this post thinking I would spend 1h and be done.

Ended up hacking non stop for a week and released http://nftarmory.me/ lol

All the code is open source for anyone interested - https://github.com/ilmoi/nft-armory
The part that specifically pulls the NFTs is in this file https://github.com/ilmoi/nft-armory/blob/main/src/common/NFTget.ts
I'm making extensive use of https://github.com/metaplex/js/ which does a lot of the stuff in this post out of the box

hope useful 🙏

@ncarrara
Copy link

Sooooooo I started with this post thinking I would spend 1h and be done.

Ended up hacking non stop for a week and released http://nftarmory.me/ lol

All the code is open source for anyone interested - https://github.com/ilmoi/nft-armory The part that specifically pulls the NFTs is in this file https://github.com/ilmoi/nft-armory/blob/main/src/common/NFTget.ts I'm making extensive use of https://github.com/metaplex/js/ which does a lot of the stuff in this post out of the box

hope useful pray

this is awesome!

@kurai021
Copy link

Would the API allow me to see the information of a whole collection? I want to make a rarity checker but I need all the metadata of a NFT collection

@muckee
Copy link

muckee commented Dec 23, 2021

For step 5, I relied on the @metaplex/js module, but there are errors in their documentation.

They suggest importing using something like the following:

import { programs } from '@metaplex/js`;
const metadata = programs.Metadata;

This is unnecessary and doesn't actually work.

Instead, you can unpack Metadata directly from the package: import { Metadata } from '@metaplex/js';

You must then declare const METADATA_PREFIX = 'metadata'; somewhere near the top of the file, below your imports.

To retrieve the data containing the metadata URL, execute the below function getNftMetadata(), passing in the public key (as a string) that you got from step 4.

These functions additionally use javascript's localStorage as a cache. If you wish to remove this functionality, the code should still work as expected. You may also uncomment the removeItem() function if you wish to utilise localStorage elsewhere in your app.

const isBrowser = (() => typeof window !== 'undefined')();

const getItem = (key) => {
  return isBrowser ? window.localStorage[key] : '';
};

const setItem = (key, value) => {
  if (isBrowser) {
    window.localStorage.setItem(key, value);
    return true;
  }
  return false;
};

// const removeItem = (key) => {
//   window.localStorage.removeItem(key);
// };

const getNftMetadata = async (mint) => {

  const provider = getProvider();

  const mintKey = new PublicKey(mint);
  const m = await getMetadata(mintKey);
  const metadataKey = new PublicKey(m);

  return await Metadata.load(provider.connection, metadataKey);
};

const getMetadata = async (
  tokenMint
) => {
  const metadataId = new PublicKey("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s");
  return (
    await findProgramAddress(
      [
        Buffer.from(METADATA_PREFIX),
        metadataId.toBuffer(),
        tokenMint.toBuffer(),
      ],
      metadataId,
    )
  )[0];
};

const findProgramAddress = async (
  seeds,
  programId
) => {
  const key =
    'pda-' +
    seeds.reduce((agg, item) => agg + item.toString('hex'), '') +
    programId.toString();
  const cached = getItem(key);
  if (cached) {
    const value = JSON.parse(cached);
    return [value.key, parseInt(value.nonce)];
  }

  const result = await PublicKey.findProgramAddress(seeds, programId);
  try {
    setItem(
      key,
      JSON.stringify({
        key: result[0].toBase58(),
        nonce: result[1],
      }),
    );
  } catch {
    // ignore
  }
  return [result[0].toBase58(), result[1]];
};

@illiaprto
Copy link

Does this work with CMv2? I got this error
'MetaplexError: Invalid owner'

Please suggest. Thanks

@muckee
Copy link

muckee commented Jan 6, 2022

@illiaprto
I haven't played with v2 yet.

Where is the error being thrown? I can have a look.

@alfonsodelarosa4
Copy link

Problem and Solution:
Problem: had a buffer problem with findProgramAddress()
ERROR: "Buffer is not defined ..."

Solution:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment