Web3.js: How to Retrieve and Display an NFT (ERC-721/ERC-1155)

You’ve heard of OpenSea and other places to view your NFTs, they read data from the public blockchain so you can view and sell the bored apes that your account holds. Knowing this data is publicly available, how can we build such a dashboard ourselves? Let’s introduce the most commonly used standards for NFTs first;

Regular, fungible tokens on the Ethereum network typically utilize ERC-20, which we’ve already covered before. NFTs typically use ERC-721 or ERC-1155, which, if you haven’t heard of ERCs before, are standards that other developers can utilize, so our code can be treated the same by various interfaces and Dapps.

ERC-721 and ERC-1155

We can use both standards to deploy a non-fungible token to the network. CryptoKitties was one of the first major NFTs to use the ERC-721 spec. Since then, ERC-1155 has proven to be a more extended and versatile version of the ERC-721 spec, allowing you to create ‘regular’ fungible tokens, like ERC-20 would enable you to, while also allowing non-fungible items to exist. Using this standard could be helpful in games where you might have fungible ‘tokens’ such as iron and gold, but also have non-fungible items like a rare outfit or ownership of a plot of land.

If you want to deploy an NFT, ERC-721 has got you covered. Most popular NFTs like Bored Ape Yacht Club use this spec, but the improved ERC-1155 spec will see increased usage in the (near) future, be it for art NFTs or on-chain games.

Images and Metadata

Whether you’re interacting with an ERC-721 or ERC-1155 token, they’ll both refer to a .json file detailing the metadata of the token/NFT you’re viewing. This file would typically contain the name of the NFT, a description, and an image URL (it could be on IPFS!). It could also include additional info, such as the strength level of a weapon in a game.

{
   "name": "Gouda Cheese",
   "description": "A sweet and creamy cheese from the Netherlands; one of the oldest recorded cheeses in the world still made today.",
   "image": "https://cheese.example/gouda.png",
   "attributes": {
      "trait_type": "Tastiness",
      "value": 100
   }
}

Setting Up Our Project

Create a new folder in which we can work on our project, then install web3js using npm.


npm install web3

Ensure you have your Infura account set up and that you have access to your endpoint URL. Feel free to read more about getting started with Infura.

At the top of our new .js file, we can add the following to import the web3js library that we just installed, and connect to our Infura endpoint:


const Web3 = require('web3');

const web3 = new Web3(new Web3.providers.HttpProvider('https://mainnet.infura.io/v3/<YOUR_PROJECT_ID>'));

Make sure to replace <YOUR_PROJECT_ID> with your actual Infura project ID.

Retrieving an ERC-721 NFT

Since we can’t read plain bytecode, we need to set the ABI to be able to read a smart contract’s functions on Ethereum.

For ERC-721 NFTs, we will utilize the tokenURI function, which is part of the ERC-721 standard. This function retrieves a token’s metadata so we can view it in our program. For ERC-1155, we can use the uri method.

Remember that the tokenURI method is optional, meaning that not every ERC-721 contract might implement it. However, this method is the only standardized on-chain way of seeing an NFTs metadata without using a third-party API.

We can use the following ABI to access this function for ERC-721:

const tokenURIABI = [
    {
        "inputs": [
            {
                "internalType": "uint256",
                "name": "tokenId",
                "type": "uint256"
            }
        ],
        "name": "tokenURI",
        "outputs": [
            {
                "internalType": "string",
                "name": "",
                "type": "string"
            }
        ],
        "stateMutability": "view",
        "type": "function"
    }
];

Retrieving the Metadata

Let’s define our ERC-721 contract address, along with the ID of a token that we would like to see the metadata of:

const tokenContract = "0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d" // BAYC contract address
const tokenId = 101 // A token we'd like to retrieve its metadata of

const contract = new web3.eth.Contract(tokenURIABI, tokenContract)

To actually retrieve the JSON file containing the metadata, we can call the tokenURI function as we defined in our ABI:

async function getNFTMetadata() {
    const result = await contract.methods.tokenURI(tokenId).call()

    console.log(result); // ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/101
}

getNFTMetadata()

It’ll return the metadata URL, which in our case is an IPFS hash. Depending on the contract, it might also return a regular HTTP URL. See the Displaying the NFT chapter to see how to view it!

Retrieving an ERC-1155 NFT

Just like ERC-721, ERC-1155 smart contracts also contain a function to retrieve an NFT’s metadata. For this ERC, we can call the uri function.

Remember that the uri method is optional, meaning that not every ERC-1155 contract might implement it. However, this method is the only standardized on-chain way of seeing an NFTs metadata without using a third-party API.

We can use the following ABI to access this function for ERC-1155:

const uriABI = [
    {
        "constant":true,
        "inputs":[
            {
                "internalType": "uint256",
                "name": "_id",
                "type": "uint256"
            }
        ],
        "name": "uri",
        "outputs":[
            {
                "internalType":"string",
                "name":"",
                "type":"string"
            }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    }
];

Retrieving the Metadata

We can define the ERC-1155 contract address, along with the ID of a token we’d like to retrieve the metadata of:

const tokenContract = "0x76be3b62873462d2142405439777e971754e8e77" // Parallel contract address
const tokenId = 10570 // A token we'd like to retrieve its metadata of

Then, to actually call the uri function to retrieve the metadata, we can use the following:

const contract = new web3.eth.Contract(uriABI, tokenContract)

async function getNFTMetadata() {
    const result = await contract.methods.uri(tokenId).call()

    console.log(result); // https://nftdata.parallelnft.com/api/parallel-alpha/ipfs/QmSwnqTmpwvZH51Uv47opPUxGYx2nknYmGoKoRJQRMDcLL
}

getNFTMetadata()

Displaying the NFT

We now have the URL pointing to the metadata of the NFT! Next, let’s explore how we can access an IPFS hash, as this is what our ERC-721 contract returned. The ERC-1155 contract actually returned a regular HTTP URL, which we can already directly access. Remember that some contracts might return a regular HTTP URL, while others might return an IPFS hash.

Accessing IPFS

We can use Infura’s IPFS endpoint to see the NFTs metadata. In your Infura account, create a new IPFS project. Enable your Dedicated Gateways, and you’ll be able to access your IPFS by using your unique subdomain.

We can create a function to combine our IPFS hash and Infura endpoint together. This will filter the ipfs:// part of the hash, and replace it with our endpoint:

function addIPFSProxy(ipfsHash) {
    const URL = "https://<YOUR_SUBDOMAIN>.infura-ipfs.io/ipfs/"
    const hash = ipfsHash.replace(/^ipfs?:\/\//, '')
    const ipfsURL = URL + hash

    console.log(ipfsURL)
    return ipfsURL
}

Make sure to update <YOUR_SUBDOMAIN> with your actual Infura IPFS subdomain.

Back in our getNFTMetadata function, we can add the following:

const ipfsURL = addIPFSProxy(result);

Requesting the Metadata

In the previous step, we added the Infura endpoint to the IPFS hash we received. Whether the contract returned an IPFS hash or a regular HTTP URL, we can now send a request to retrieve the metadata:

const request = new Request(ipfsURL);
const response = await fetch(request);
const metadata = await response.json();
console.log(metadata);

We will see the following JSON appearing in our terminal upon running the script:

{
  image: 'ipfs://QmNdvtT9EmrUc6haJyN7ZanHNrsjd23v1ydG6r8jTGEZvq',
  attributes: [
    { trait_type: 'Clothes', value: 'Navy Striped Tee' },
    { trait_type: 'Hat', value: "Fisherman's Hat" },
    { trait_type: 'Fur', value: 'Gray' },
    { trait_type: 'Background', value: 'Army Green' },
    { trait_type: 'Eyes', value: 'Eyepatch' },
    { trait_type: 'Mouth', value: 'Bored' }
  ]
}

Great! We can now access the image with the image key. Again, some contracts might use an IPFS hash, and some might put a regular HTTP URL to a .png or .jpg.

To access this IPFS hash, we can use the same function we defined earlier to access it through Infura:


const image = addIPFSProxy(metadata.image);

Our addIPFSProxy function will return the IPFS hash combined with our Infura endpoint, after which we can directly access this in our browser to view the NFT!

Complete Code Overview

ERC-721 (with IPFS)

const Web3 = require('web3');
const web3 = new Web3(new Web3.providers.HttpProvider('https://mainnet.infura.io/v3/<YOUR_PROJECT_ID>'));

const tokenURIABI = [
    {
        "inputs": [
            {
                "internalType": "uint256",
                "name": "tokenId",
                "type": "uint256"
            }
        ],
        "name": "tokenURI",
        "outputs": [
            {
                "internalType": "string",
                "name": "",
                "type": "string"
            }
        ],
        "stateMutability": "view",
        "type": "function"
    }
];

const tokenContract = "0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d" // BAYC contract address
const tokenId = 101 // A token we'd like to retrieve its metadata of

const contract = new web3.eth.Contract(tokenURIABI, tokenContract)

async function getNFTMetadata() {
    const result = await contract.methods.tokenURI(tokenId).call()

    console.log(result); // ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/101

    const ipfsURL = addIPFSProxy(result);

    const request = new Request(ipfsURL);
    const response = await fetch(request);
    const metadata = await response.json();
    console.log(metadata); // Metadata in JSON

    const image = addIPFSProxy(metadata.image);
}

getNFTMetadata()

function addIPFSProxy(ipfsHash) {
    const URL = "https://<YOUR_SUBDOMAIN>.infura-ipfs.io/ipfs/"
    const hash = ipfsHash.replace(/^ipfs?:\/\//, '')
    const ipfsURL = URL + hash

    console.log(ipfsURL) // https://<subdomain>.infura-ipfs.io/ipfs/<ipfsHash>
    return ipfsURL
}

ERC-1155 (without IPFS)

The contract in the below example doesn’t return an IPFS hash, but if you do run into an ERC-1155 contract that uses IPFS, you can use the same IPFS code as in the ERC-721 example above :slight_smile:

const Web3 = require('web3');
const web3 = new Web3(new Web3.providers.HttpProvider('https://mainnet.infura.io/v3/<YOUR_PROJECT_ID>'));

const uriABI = [
    {
        "constant":true,
        "inputs":[
            {
                "internalType": "uint256",
                "name": "_id",
                "type": "uint256"
            }
        ],
        "name": "uri",
        "outputs":[
            {
                "internalType":"string",
                "name":"",
                "type":"string"
            }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    }
];

const tokenContract = "0x76be3b62873462d2142405439777e971754e8e77" // Parallel contract address
const tokenId = 10570 // A token we'd like to retrieve its metadata of

const contract = new web3.eth.Contract(uriABI, tokenContract)

async function getNFTMetadata() {
    const result = await contract.methods.uri(tokenId).call()

    console.log(result); // https://nftdata.parallelnft.com/api/parallel-alpha/ipfs/QmSwnqTmpwvZH51Uv47opPUxGYx2nknYmGoKoRJQRMDcLL
}

getNFTMetadata()
4 Likes

I would like some help. What should I do after verifying ownership of BSC smart contracts?

I have a smart contract with a large number of BNB payments and this has been confirmed by MyCrypto. What should I do to get that smart contract to my public wallet?

@payephyoeaung87 Hey there! Did you deploy this smart contract? If not, I’d highly recommend reaching out to the owner of the contract, who might be able to help you further.

No, I’m the owner of the contract, but I do not issue the Crypto tokens in the contract

hello
Is it accurate to judge whether the contract is erc-1155 or erc-721?
Is it possible that there just happens to be a contract that has a tokenURL method?

@apollo This is a great question! This could be possible, but there is a way to better detect whether you’re actually reading an NFT event. To make sure that the contract you’re interacting with is either ERC-721 or ERC-1155, I’d highly recommend taking a look at ERC-165, which helps you detect whether a contract includes a specified set of interfaces.

Both standards are required to include the ERC-165 spec, so you can be sure that the contract you’re interacting with is either of those NFT specifications :slight_smile:

I haven’t found a good method so far, especially for proxy contracts, which is more difficult to judge. Didn’t find a real authoritative answer.