Rarity-Ranking-NFT Error

Hello,

I have followed the youtube video: https://www.youtube.com/watch?v=TXpfRRHwjak

and have cloned the repo: https://github.com/IAmJaysWay/Rarity-Ranking-NFT

When I am running /main/generator/main.js for a new nft collection to calculate rarity, I am getting the error below:

TypeError: Cannot read property ‘attributes’ of null
at C:\Code\rarity-ranking-nft\generator\main.js:39:59
at Array.map ()
at generateRarity (C:\Code\rarity-ranking-nft\generator\main.js:39:26)

This happened on a few collections, but here is a one of the contracts I used as an example: 0x75e95ba5997eb235f40ecf8347cdb11f18ff640b

Is there some workaround code that I can add to fix this?

it looks like this is the line with that error:

  let metadata = allNFTs.map((e) => JSON.parse(e.metadata).attributes);

maybe there are some token ids that don’t have metadata and maybe that is why it gives that error

Yes, this is common and shouldn’t cause it to break the code. I want to know if there is a workaround that I can add to avoid this from breaking the code.

I guess that you could modify some lines of code there to handle that case

I ran into the same problem.

   {
      token_address: '0x6632a9d63e142f17a668064d41a21193b49b41a0',
      token_id: '7879',
      amount: '1',
      contract_type: 'ERC721',
      name: 'Prime Ape Planet',
      symbol: 'PAP',
      token_uri: 'https://primeapeplanet.com/metadata/7879',
      metadata: '{"name":"Prime Ape #7879","description":"A unique collection of 7,979 3D hand-drawn Prime Apes, stored on the Ethereum network.","image":"https://primeapeplanet.com/images/prereveal.png","externalUrl":"https://primeapeplanet.com"}',
      synced_at: '2021-12-28T17:38:03.162Z'
    },
    ... 400 more items
  ]
}
7998
500
here
TypeError: Cannot read property 'attributes' of null
    at /home/amer/ethereum/dapp_marketplace/generator/main.js:42:59
    at Array.map (<anonymous>)
    at generateRarity (/home/amer/ethereum/dapp_marketplace/generator/main.js:42:26)

when i check the token_uri I can see the attributes are in the metadata


{
  "description": "A unique collection of 7,979 3D hand-drawn Prime Apes, stored on the Ethereum network.",
  "external_url": "https://primeapeplanet.com",
  "image": "https://primeapeplanet.com/images/7879.png",
  "name": "#7879",
  "attributes": [
    {
      "trait_type": "Clothing",
      "value": "T-Shirt camouflage"
    },
    {
      "trait_type": "Emotion",
      "value": "Seducing"
    },
    {
      "trait_type": "Eyes",
      "value": "Brown"
    },
    {
      "trait_type": "Skin",
      "value": "Light Brown"
    },
    {
      "trait_type": "Hat",
      "value": "Beanie Black"
    }
  ]
}

is this an issue with the moralis data or do I need to code around this?

I am having the same problem. The attributes are in the metadata array so I’m not sure what could be the issue.

I think it is related to how the json files are structured. When this line is ran:

let metadata = allNFTs.map((e) => JSON.parse(e.metadata).attributes);

The allNFTs array is converted to javascript objects. I believe because the array contained nested items those are converting incorrectly. If you remove

.attributes
let metadata = allNFTs.map((e) => JSON.parse(e.metadata));
console.log(attributes);

And then run the above code you will get output like this:

  {
    name: 'MetaSpecies #781',
    description: 'The MetaSpecies NFT Project',
    image: 'ipfs://QmVtAYRKsdKHHFDJzeCWMTs2rs1MzQ9wvokMxwkR8BSTST/781.png',
    dna: '9b481b9beb790e6b776d8066ad14edc90acd23b1',
    edition: 781,
    date: 1638160863460,
    attributes: [
      [Object], [Object],
      [Object], [Object],
      [Object], [Object],
      [Object], [Object]
    ],

I don’t think the objects are structured correctly for the code to be able to parse it. I’ve been looking for a solution for awhile. I even updated the metadata on my smart contract to remove line breaks however now I’m having issues with my metadata being out of sync from what is actually on chain. I made a forum post addressing that issue specifically:

https://forum.moralis.io/t/typeerror-moralis-web3api-token-resyncmetadata-is-not-a-function/9427

I do believe this caused by the line breaks in my metadata. I am resyncing my entire collection right but it will take about 12 hours to complete. Once I have my updated metadata without the line breaks I will try this again and update everyone. :slight_smile:

Has anyone found a solution for this? I’ll attach the code from my main file, I can not work this out!!!

This is my error:

C:\Users\user\Documents\Crypto\NFT Dev\Moralis\CSApp\Rarity-Ranking-NFT\generator>node main.js
10000
500
TypeError: Cannot read properties of null (reading 'attributes')
    at C:\Users\user\Documents\Crypto\NFT Dev\Moralis\CSApp\Rarity-Ranking-NFT\generator\main.js:38:59
    at Array.map (<anonymous>)
    at generateRarity (C:\Users\user\Documents\Crypto\NFT Dev\Moralis\CSApp\Rarity-Ranking-NFT\generator\main.js:38:26)
const Moralis = require("moralis/node");
const { timer } = require("rxjs");

const serverUrl = ""; //Moralis Server Url here
const appId = ""; //Moralis Server App ID here
Moralis.start({ serverUrl, appId });

const resolveLink = (url) => {
  if (!url || !url.includes("ipfs://")) return url;
  return url.replace("ipfs://", "https://gateway.ipfs.io/ipfs/");
};

const collectionAddress = "0xc1caf0c19a8ac28c41fe59ba6c754e4b9bd54de9"; //Collection Address Here
const collectionName = "CryptoSkulls"; //CollectioonName Here

async function generateRarity() {
  const NFTs = await Moralis.Web3API.token.getAllTokenIds({
    address: collectionAddress,
  });

  const totalNum = NFTs.total;
  const pageSize = NFTs.page_size;
  console.log(totalNum);
  console.log(pageSize);
  let allNFTs = NFTs.result;

  const timer = (ms) => new Promise((res) => setTimeout(res, ms));

  for (let i = pageSize; i < totalNum; i = i + pageSize) {
    const NFTs = await Moralis.Web3API.token.getAllTokenIds({
      address: collectionAddress,
      offset: i,
    });
    allNFTs = allNFTs.concat(NFTs.result);
    await timer(6000);
  }

  let metadata = allNFTs.map((e) => JSON.parse(e.metadata).attributes);

  let tally = { TraitCount: {} };

  for (let j = 0; j < metadata.length; j++) {
    let nftTraits = metadata[j].map((e) => e.trait_type);
    let nftValues = metadata[j].map((e) => e.value);

    let numOfTraits = nftTraits.length;

    if (tally.TraitCount[numOfTraits]) {
      tally.TraitCount[numOfTraits]++;
    } else {
      tally.TraitCount[numOfTraits] = 1;
    }

    for (let i = 0; i < nftTraits.length; i++) {
      let current = nftTraits[i];
      if (tally[current]) {
        tally[current].occurences++;
      } else {
        tally[current] = { occurences: 1 };
      }

      let currentValue = nftValues[i];
      if (tally[current][currentValue]) {
        tally[current][currentValue]++;
      } else {
        tally[current][currentValue] = 1;
      }
    }
  }

  const collectionAttributes = Object.keys(tally);
  let nftArr = [];
  for (let j = 0; j < metadata.length; j++) {
    let current = metadata[j];
    let totalRarity = 0;
    for (let i = 0; i < current.length; i++) {
      let rarityScore =
        1 / (tally[current[i].trait_type][current[i].value] / totalNum);
      current[i].rarityScore = rarityScore;
      totalRarity += rarityScore;
    }

    let rarityScoreNumTraits =
      8 * (1 / (tally.TraitCount[Object.keys(current).length] / totalNum));
    current.push({
      trait_type: "TraitCount",
      value: Object.keys(current).length,
      rarityScore: rarityScoreNumTraits,
    });
    totalRarity += rarityScoreNumTraits;

    if (current.length < collectionAttributes.length) {
      let nftAttributes = current.map((e) => e.trait_type);
      let absent = collectionAttributes.filter(
        (e) => !nftAttributes.includes(e)
      );

      absent.forEach((type) => {
        let rarityScoreNull =
          1 / ((totalNum - tally[type].occurences) / totalNum);
        current.push({
          trait_type: type,
          value: null,
          rarityScore: rarityScoreNull,
        });
        totalRarity += rarityScoreNull;
      });
    }

    if (allNFTs[j].metadata) {
      allNFTs[j].metadata = JSON.parse(allNFTs[j].metadata);
      allNFTs[j].image = resolveLink(allNFTs[j].metadata.image);
    } else if (allNFTs[j].token_uri) {
      try {
        await fetch(allNFTs[j].token_uri)
          .then((response) => response.json())
          .then((data) => {
            allNFTs[j].image = resolveLink(data.image);
          });
      } catch (error) {
        console.log(error);
      }
    }

    nftArr.push({
      Attributes: current,
      Rarity: totalRarity,
      token_id: allNFTs[j].token_id,
      image: allNFTs[j].image,
    });
  }

  nftArr.sort((a, b) => b.Rarity - a.Rarity);

  for (let i = 0; i < nftArr.length; i++) {
    nftArr[i].Rank = i + 1;
    const newClass = Moralis.Object.extend(collectionName);
    const newObject = new newClass();

    newObject.set("attributes", nftArr[i].Attributes);
    newObject.set("rarity", nftArr[i].Rarity);
    newObject.set("tokenId", nftArr[i].token_id);
    newObject.set("rank", nftArr[i].Rank);
    newObject.set("image", nftArr[i].image);

    await newObject.save();
    console.log(i);
  }
  
  return true
}

generateRarity()
.then( ( result ) => { console.log( result ) } )
.catch( ( error ) => { console.log( error ) } )

i have this same issue, did you ever figure out the generator?

PS C:\Users\nrwis\Desktop\rarity\rarity-ranking-NFT\generator> node main.js
1000
500
TypeError: Cannot read properties of null (reading 'attributes')
    at C:\Users\nrwis\Desktop\rarity\rarity-ranking-NFT\generator\main.js:38:59
    at Array.map (<anonymous>)
    at generateRarity (C:\Users\nrwis\Desktop\rarity\rarity-ranking-NFT\generator\main.js:38:26)```

Can you post your code please? I was not able to replicate the issue with @Kelsier’s code either - my only error is offset must be less than 1000 ... as this tutorial is outdated.

Make sure the collection you’re using has attributes in the metadata for every NFT.

Hey Man,

I found it has to with poor metadata structure that the NFT Collection owner itself has used.

To get the metadata from the jsons to show up on opensea etc that only have to have certain things in certain orders and naming. But when the json has some messy elements in it, you have to add some manual code to take that into account.

Hope that helps, maybe posting an example of the NFTs metadata for the other devs will help them identify the problem.

here is how the metadata looks that is on Ipfs for token Uri. Its my own collections im trying to add.

{"name":"Bio Apes #3","description":"Apes from a reclusive tribe were violently abducted by the Cyborx and subjected to ruthless experiments. \n\nAfter escaping the laboratory, these Bio Apes were banished from their tribe, and now viciously hunt Cyborx for revenge\n\n1800 Bio Apes Living on Ethereum.",
"fee_recipient":"0x7fd9D504f1cCCf5085c51873aC6813feA4b479d9","seller_fee_basis_points":800,"image":"ipfs://URI HERE/.png",
"external_url":"","attributes":[{"trait_type":"1. Backgrounds","value":"Sunset"},{"trait_type":"3. Body","value":"Pastel Blue"},{"trait_type":"4. Face Details","value":"Fit Face"},{"trait_type":"8. Eyes","value":"Innocent"},
{"trait_type":"10. Mouths","value":"Syringes"}],"hash":"dc9a03a069936741ce2b3f5bd8537f22"}

here is my main.js, i managed to get passed that and it starts generating traits but then it says undefined and it quits and nothing more happens. its not the same as the tutorial becuase thats out of date. Slowly its getting better.

const Moralis = require("moralis/node");
const { timer } = require("rxjs");

const serverUrl = ""; //Moralis Server Url here
const appId = ""; //Moralis Server App ID here
Moralis.start({ serverUrl, appId });

const resolveLink = (url) => {
  if (!url || !url.includes("ipfs://")) return url;
  return url.replace("ipfs://", "https://gateway.ipfs.io/ipfs/");
};

const collectionAddress = "0x01485E923e51F6ABb2b48aAa310d357B429Ba263"; //Collection Address Here
const collectionName = "Cyborx"; //CollectioonName Here





async function generateRarity() {
  const NFTs = await Moralis.Web3API.token.getAllTokenIds({
    address: collectionAddress,
  });


  const totalNum = NFTs.total;
  const pageSize = NFTs.page_size;
  console.log(totalNum);
  console.log(pageSize);
  let allNFTs = NFTs.result;

  const timer = (ms) => new Promise((res) => setTimeout(res, ms));

  for (let i = pageSize; i < totalNum; i = i + pageSize) {
    const NFTs = await Moralis.Web3API.token.getAllTokenIds({
      address: collectionAddress,
      offset: i,
    });
    allNFTs = allNFTs.concat(NFTs.result);
    await timer(9000);
  }

  let collection = allNFTs.map(e => {
    return {
    metadata: JSON.parse(e.metadata),
    token_id: e.token_id
    };
    });
    
    let metadata = collection.map(e => e.metadata);
    console.log(metadata.length);
    
    for (let i = 0; i < metadata.length; i++) {
    console.log(metadata[i]?.attributes, i);
    }
  console.log(attributes);
  console.log(error);

  let tally = { TraitCount: {} };

  for (let j = 0; j < metadata.length; j++) {
    let nftTraits = metadata[j].map((e) => e.trait_type);
    let nftValues = metadata[j].map((e) => e.value);

    let numOfTraits = nftTraits.length;

    if (tally.TraitCount[numOfTraits]) {
      tally.TraitCount[numOfTraits]++;
    } else {
      tally.TraitCount[numOfTraits] = 1;
    }

    for (let i = 0; i < nftTraits.length; i++) {
      let current = nftTraits[i];
      if (tally[current]) {
        tally[current].occurences++;
      } else {
        tally[current] = { occurences: 1 };
      }

      let currentValue = nftValues[i];
      if (tally[current][currentValue]) {
        tally[current][currentValue]++;
      } else {
        tally[current][currentValue] = 1;
      }
    }
  }

  const collectionAttributes = Object.keys(tally);
  let nftArr = [];
  for (let j = 0; j < metadata.length; j++) {
    let current = metadata[j];
    let totalRarity = 0;
    for (let i = 0; i < current.length; i++) {
      let rarityScore =
        1 / (tally[current[i].trait_type][current[i].value] / totalNum);
      current[i].rarityScore = rarityScore;
      totalRarity += rarityScore;
    }

    let rarityScoreNumTraits =
      8 * (1 / (tally.TraitCount[Object.keys(current).length] / totalNum));
    current.push({
      trait_type: "TraitCount",
      value: Object.keys(current).length,
      rarityScore: rarityScoreNumTraits,
    });
    totalRarity += rarityScoreNumTraits;

    if (current.length < collectionAttributes.length) {
      let nftAttributes = current.map((e) => e.trait_type);
      let absent = collectionAttributes.filter(
        (e) => !nftAttributes.includes(e)
      );

      absent.forEach((type) => {
        let rarityScoreNull =
          1 / ((totalNum - tally[type].occurences) / totalNum);
        current.push({
          trait_type: type,
          value: null,
          rarityScore: rarityScoreNull,
        });
        totalRarity += rarityScoreNull;
      });
    }

    if (allNFTs[j].metadata) {
      allNFTs[j].metadata = JSON.parse(allNFTs[j].metadata);
      allNFTs[j].image = resolveLink(allNFTs[j].metadata.image);
    } else if (allNFTs[j].token_uri) {
      try {
        await fetch(allNFTs[j].token_uri)
          .then((response) => response.json())
          .then((data) => {
            allNFTs[j].image = resolveLink(data.image);
          });
      } catch (error) {
        console.log(error);
      }
    }

    nftArr.push({
      Attributes: current,
      Rarity: totalRarity,
      token_id: allNFTs[j].token_id,
      image: allNFTs[j].image,
    });
  }

  nftArr.sort((a, b) => b.Rarity - a.Rarity);

  for (let i = 0; i < nftArr.length; i++) {
    nftArr[i].Rank = i + 1;
    const newClass = Moralis.Object.extend(collectionName);
    const newObject = new newClass();

    newObject.set("attributes", nftArr[i].Attributes);
    newObject.set("rarity", nftArr[i].Rarity);
    newObject.set("tokenId", nftArr[i].token_id);
    newObject.set("rank", nftArr[i].Rank);
    newObject.set("image", nftArr[i].image);

    await newObject.save();
    console.log(i);
  }
  
  return true
}

generateRarity()
.then( ( result ) => { console.log( result ) } )
.catch( ( error ) => { console.log( error ) } )

Thanks. There is a problem with these two lines as attributes and error don’t exist, so remove them:

console.log(attributes);
console.log(error);

Then I get metadata[j].map is not a function, logging metadata[j] says it’s an object so this will fail as it’s not an array. Change this so we map over the attributes array:

let nftTraits = metadata[j].attributes.map((e) => e.trait_type);
let nftValues = metadata[j].attributes.map((e) => e.value);

There’s more errors after this that are similar (not using the attributes array), I’m not sure whether this is due to the collection you’re using in particular or an issue with the original code, but you get the idea.

Awesome ill give that a try. the little things. I forgot to remove the console logs from previous trys. thanks for the reply!

interesting, now its returning more in results in the terminal. and the numbers are in order and alot more of them, but go to 999 and returned the error.

[ { trait_type: 'Backround', value: 'Glynstone Galaxy' }, { trait_type: 'Body', value: 'Black' }, { trait_type: 'Tattoo', value: 'Neck Circuit' }, { trait_type: 'Robotics', value: 'Highspeed Dual Robotics' }, { trait_type: 'Helmet Led', value: 'Purple' }, { trait_type: 'Helmet', value: 'Smooth Gold' }, { trait_type: 'Eye Frame', value: 'Black Hole' }, { trait_type: 'Eyes', value: 'Quantum Orange' }, { trait_type: 'Weapon Belt', value: 'Shoulder' } ] 999 TypeError: Cannot read properties of null (reading 'attributes') at generateRarity (C:\Users\nrwis\Desktop\rarity\rarity-ranking-nft\generator\main.js:60:33)

I think there is an issue with how the metadata object is being grouped earlier in the code or it’s not being pulled properly. I want to say it’s because 1 or more of the tokens in this collection don’t have synced metadata.

Trying wrapping that code block in an if (metadata[j]) to get past it for now.

I want to say it’s because 1 or more of the tokens in this collection don’t have synced metadata.

Yes this was the issue for one of the tokens (325). You can use reSyncMetadata to update it.

Doing that should get you past a few of these errors.

1 Like