Rarity-Ranking-NFT Error

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

:wave:Hi, Iā€™ve been trying to get this script working Iā€™ve changed out some code to deal with the new pagination and have followed along with the the attributes thread above.So Iā€™ve added the MoralisSecret to the await inside the generateRarity function. Add updated the pagination replacing offset with the new do code block I think I found on the youtube video comments. But unfortunately still getting an error when it comes to current.push.

My error now is TypeError: current.push is not a function

here is my main.js

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

const serverUrl = ""; //Moralis Server Url here
const appId = ""; //Moralis Server App ID here
const moralisSecret = "";



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

//const collectionAddress = "0x343b68141129ec115c1fc523C5Ae90586fe95b77"; //Collection Address Here
//const collectionName = "NFTisse"; //CollectioonName Here

const collectionAddress = "0x0dD0CFeAE058610C88a87Da2D9fDa496cFadE108"; //Collection Address Here
const collectionName = "SoupX"; //CollectioonName Here



async function generateRarity() {
    await Moralis.start({ serverUrl, appId, moralisSecret });
    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));

    let cursor = null;

    do {
        const NFTs = await Moralis.Web3API.token.getAllTokenIds({
            address: collectionAddress,
            cursor: cursor,
            chain: "eth",
        });
        console.log(`Got page ${NFTs.page} of ${Math.ceil(NFTs.total / NFTs.page_size)}, ${NFTs.total} total`)
        allNFTs = allNFTs.concat(NFTs.result);
        await timer(9000);
        cursor = NFTs.cursor
    } while (cursor != '' && cursor != null)



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

    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].attributes.map((e) => e.trait_type);
        let nftValues = metadata[j].attributes.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 for any pointers in fixing this, would love to get this working. Hopefully between here, the discord and the youtube comments we can get a working main.js cheers this looks like an awesome rarity site boilerplate.

I now tried some if array code on current, and it ran through and listed traits in the terminal 0,1,2,3,4,5,true and back to command promptā€¦

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

const serverUrl = ""; //Moralis Server Url here
const appId = ""; //Moralis Server App ID here
const moralisSecret = "";



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

//const collectionAddress = "0x343b68141129ec115c1fc523C5Ae90586fe95b77"; //Collection Address Here
//const collectionName = "NFTisse"; //CollectioonName Here

const collectionAddress = "0x0dD0CFeAE058610C88a87Da2D9fDa496cFadE108"; //Collection Address Here
const collectionName = "SoupX"; //CollectioonName Here



async function generateRarity() {
    await Moralis.start({ serverUrl, appId, moralisSecret });
    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));

    let cursor = null;

    do {
        const NFTs = await Moralis.Web3API.token.getAllTokenIds({
            address: collectionAddress,
            cursor: cursor,
            chain: "eth",
        });
        console.log(`Got page ${NFTs.page} of ${Math.ceil(NFTs.total / NFTs.page_size)}, ${NFTs.total} total`)
        allNFTs = allNFTs.concat(NFTs.result);
        await timer(9000);
        cursor = NFTs.cursor
    } while (cursor != '' && cursor != null)



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

    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].attributes.map((e) => e.trait_type);
        let nftValues = metadata[j].attributes.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));

        if (Array.isArray(current)) {

            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) })
 if (Array.isArray(current)) {

            current.push({
                trait_type: "TraitCount",
                value: Object.keys(current).length,
                rarityScore: rarityScoreNumTraits,
            });
        }

this loaded the main page NFTs! but thereā€™s an error when I click on the NFT thumbnail. None of the rarity shows and it crashes with errors nft.rarity is not a function or nft.attributes.map is not a function If I comment out the nft rarity in the QuickStart.jsx I can get it to load the large image at the top just without any rarity info displaying.

but thereā€™s an error when I click on the NFTs.

What is the error? What do you mean by click on the NFTs, is this with the frontend?

if (Array.isArray(current)) {

If this is part of the script, make sure to update your post.

Iā€™ve updated the post above with my current main.js. and errors.

nft.attributes.map is not a function

This needs to be changed to {nft.attributes.attributes.map((e) => { since the original main.js script has been changed.

Iā€™m getting Cannot read properties of null (reading 'toFixed'):

As rarity is null, Iā€™m not sure if this is intentional or an issue with your generator script. Removing each use of toFixed gets around this error. The NFT page now works but each trait has undefined for its rarity. So thereā€™s still a few issues with the script.

nft.rarity is not a function

I donā€™t get this error when using your generator.

hmmm thanks so much for your input. yes Iā€™ve now got it working to the point that it shows all the traits on the NFT page but theyā€™re all undefined.

Iā€™m wondering if my fix for the current.push is not a function (wrapping in the if is array) is messing with
the rarities being able to render. maybe needs more code like if then elseā€¦

I am not sure where rarity is meant to come from as itā€™s not saved in the database for each NFT (at least for your script) - check with the tutorial.

Iā€™m gonna guess that the current.push issue maybe the issue then if itā€™s not pushing the rarity to the db correctly because of that. Some of the collections in the db do have a rarity column, others donā€™t, and the ones that do itā€™s the same number or null.

Hereā€™s my latest main js file. This will get all the way through and push to db but is not seem to be generating rarity. It logs out the traitcount and meta. And contains different bits of commented out code Iā€™ve been editing and testing. Regarding the QuickStart component, for now Iā€™ve removed any toFixed so that it doesnā€™t crash.

const Moralis = require("moralis/node");
const { isCompositeComponent } = require("react-dom/test-utils");
const { timer } = require("rxjs");

const serverUrl = ""; //Moralis Server Url here
const appId = ""; //Moralis Server App ID here
const moralisSecret = "";


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


const collectionAddress = "0x0dD0CFeAE058610C88a87Da2D9fDa496cFadE108"; //Collection Address Here
const collectionName = "SoupX"; //CollectioonName Here


async function generateRarity() {
    await Moralis.start({ serverUrl, appId, moralisSecret });
    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));

    let cursor = null;

    do {
        const NFTs = await Moralis.Web3API.token.getAllTokenIds({
            address: collectionAddress,
            cursor: cursor,
            chain: "eth",
        });
        console.log(`Got page ${NFTs.page} of ${Math.ceil(NFTs.total / NFTs.page_size)}, ${NFTs.total} total`)
        allNFTs = allNFTs.concat(NFTs.result);
        await timer(9000);
        cursor = NFTs.cursor
    } while (cursor != '' && cursor != null)



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

    let collection = allNFTs.map(e => {
        return {
            metadata: JSON.parse(e.metadata),
            token_id: e.token_id
        };
    });

    //console.log(collection[0]);


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

    let metadata = collection.map(e => e.metadata);
    console.log(metadata.length);

    //console.log(metadata[0]);


    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].attributes.map((e) => e.trait_type);
        let nftValues = metadata[j].attributes.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;
        }

        //console.log(metadata[0]);
        console.log(tally);

        let rarityScoreNumTraits =
            8 * (1 / (tally.TraitCount[Object.keys(current).length] / totalNum));

        //let rarityScoreNumTraits =
        //    1 / (tally.TraitCount[Object.keys(current).length] / totalNum);



        if (Array.isArray(current)) {

            current.push({
                trait_type: "TraitCount",
                value: Object.keys(current).length,
                rarityScore: rarityScoreNumTraits,
            });
        }

        //if (!current) return null

        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;

            });
        }


        //console.log(metadata[0]);


        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) })

Hereā€™s the QuickStart.jsx page code I removed the .toFixed(1) in two places so it loads. {+${e.rarityScore.toFixed(1)}} and {nft.rarity.toFixed(1)}

import { useMoralis } from "react-moralis";
import {
  Button,
  Image,
  Card,
  Badge,
  Spin,
  Alert,
  Divider,
  Select,
  Input,
} from "antd";
import { useState } from "react";

const { Meta } = Card;
const { Option } = Select;
const { Search } = Input;

const styles = {
  NFTs: {
    display: "flex",
    flexWrap: "wrap",
    WebkitBoxPack: "start",
    justifyContent: "flex-start",
    margin: "0px auto 20px",
    maxWidth: "1200px",
    width: "100%",
    gap: "10px",
  },
};

function HomePage() {
  const fallbackImg =
    "data:image/png;base64,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";

  const [token, setToken] = useState();
  const [visibility, setVisibility] = useState(false);
  const [NFTBalances, setNFTBalances] = useState();
  const [collection, setCollection] = useState();
  const [nft, setNft] = useState();
  const { Moralis } = useMoralis();




  const handleChangeCollection = async (col) => {
    const dbNFTs = Moralis.Object.extend(col);
    const query = new Moralis.Query(dbNFTs);
    query.ascending("rank");
    const topNFTs = query.limit(24);
    const results = await topNFTs.find();
    setNFTBalances(results);
  };

  const handleSelectToken = async (num, col) => {
    if (num && col) {
      const dbNFTs = Moralis.Object.extend(col);
      const query = new Moralis.Query(dbNFTs);
      console.log(num);
      query.equalTo("tokenId", num);
      let selectedNFT = await query.first();
      selectedNFT = selectedNFT.attributes;
      console.log(selectedNFT);
      setNft(selectedNFT);
      setVisibility(true);
    }
  };

  const collectionChanged = async (col) => {
    setCollection(col);
    handleSelectToken(token, col);
    handleChangeCollection(col);
  };

  const addToNFTs = async (col) => {
    const dbNFTs = Moralis.Object.extend(col);
    const query = new Moralis.Query(dbNFTs);
    query.ascending("rank");
    query.limit(6);
    const topNFTs = query.skip(NFTBalances.length);
    const results = await topNFTs.find();
    setNFTBalances(NFTBalances.concat(results));
  }

  return (
    <>
      <div>
        <div
          style={{
            display: "flex",
            justifyContent: "center",
            margin: "0px auto 30px",
            maxWidth: "1200px",
            width: "100%",
            gap: "10px",
          }}
        >
          <Select
            showSearch
            style={{ width: "500px" }}
            placeholder="Select Collection"
            onChange={(e) => collectionChanged(e)}
          >
            <Option value="BBS"> Bossy Baddies </Option>
            <Option value="SoupX"> SoupX </Option>
            <Option value="MoonbirdsBatz"> MoonbirdsBatz </Option>
            <Option value="Nonfungiblesoup"> Nonfungiblesoup </Option>
            <Option value="JPF"> J. Pierce and Friends </Option>
            <Option value="FLIP"> Flip it Like It's Hot </Option>
            <Option value="RMutt"> R. Mutt </Option>
            <Option value="RMutt2"> R. Mutt 2 </Option>
            <Option value="DigiDaigaku"> DigiDaigaku </Option>

          </Select>
          <Search
            style={{ width: "250px" }}
            placeholder="Search For Token"
            onChange={(e) => setToken(e.target.value)}
            onSearch={() => handleSelectToken(token, collection)}
            enterButton
          />
        </div>
        <Divider />
        {visibility && (
          <>
            <div
              style={{
                display: "flex",
                justifyContent: "space-evenly",
                marginBottom: "30px",
                width: "80vw",
              }}
            >
              <Badge.Ribbon
                text={`Rank #${nft.rank}`}
                style={{ fontSize: "18px" }}
              >
                <Image
                  preview={false}
                  src={nft.image}
                  loading="lazy"
                  placeholder={
                    <div
                      style={{
                        backgroundColor: "rgba(0, 0, 0, 0.2)",
                        borderRadius: "18px",
                      }}
                    >
                      <Spin
                        size="large"
                        style={{
                          margin: "auto",
                          padding: "250px 0",
                          width: "640px",
                          height: "640px",
                        }}
                      />
                    </div>
                  }
                  fallback={fallbackImg}
                  alt=""
                  style={{ height: "640px" }}
                />
              </Badge.Ribbon>
              <Card
                title={`${collection} #${nft.tokenId}`}
                bordered={false}
                style={{ width: "350px" }}
              >
                <div
                  style={{
                    backgroundColor: "#91d5ff",
                    height: "100px",
                    borderRadius: "10px",
                    marginBottom: "10px",
                    display: "flex",
                    flexDirection: "column",
                    alignItems: "center",
                    color: "white",
                    fontWeight: "bold",
                    fontSize: "24px",
                  }}
                >
                  Non-Fungi Rarity Score
                  <div
                    style={{
                      backgroundColor: "white",
                      borderRadius: "8px",
                      width: "98%",
                      margin: "auto",
                      textAlign: "center",
                      fontWeight: "bold",
                      fontSize: "20px",
                      color: "green",
                      marginTop: "2px",
                    }}
                  >
                    {nft.rarity}
                  </div>
                  <div
                    style={{
                      textAlign: "center",
                      fontSize: " 10px",
                      fontWeight: "normal",
                      paddingBottom: "2px",
                    }}
                  >
                    Non-Fungi NFT Ranking
                  </div>
                </div>
                {nft.attributes.attributes.map((e) => {
                  return (
                    <>
                      <div
                        style={{
                          display: "flex",
                          justifyContent: "space-between",
                          fontWeight: "bold",
                        }}
                      >
                        <span style={{ color: "gray" }}>{e.trait_type}</span>
                        <span
                          style={{ color: "green", paddingRight: "4%" }}
                        >{`+${e.rarityScore}`}</span>
                      </div>
                      <Alert
                        style={{
                          padding: "2px 2px 2px 12px",
                          width: "98%",
                          margin: "0px auto 5px",
                          fontSize: "14px",
                        }}
                        message={e.value ? e.value : "<null>"}
                        type="info"
                        action={
                          <Button
                            size="small"
                            style={{
                              display: "flex",
                              justifyContent: "end",
                              width: "60px",
                            }}
                          >
                            {e.trait_type === "TraitCount" ?
                              ((8 * (10000 / e.rarityScore)).toFixed(0)) :  //Only use this if rarity generator adjusted to 8x traitcount
                              ((10000 / e.rarityScore).toFixed(0))         //Also must be adjusted for collections with +- 10000 NFTs
                            }
                          </Button>
                        }
                      />
                    </>
                  );
                })}
              </Card>
            </div>
          </>
        )}
        <Divider />
        <div
          style={{
            fontSize: " 30px",
            fontWeight: "bold",
            color: "#69c0ff",
          }}
        >
          Collection Ranked By Rarity
        </div>
        <div
          style={{
            fontSize: " 20px",
            color: "#bfbfbf",
          }}
        >
          {collection}
        </div>
        <Divider />
        <div style={styles.NFTs}>
          {NFTBalances &&
            NFTBalances.map((nft, index) => {
              return (
                <Card
                  onClick={() =>
                    handleSelectToken(nft.attributes.tokenId, collection)
                  }
                  hoverable
                  style={{ width: 190, border: "2px solid #e7eaf3" }}
                  cover={
                    <Image
                      preview={false}
                      src={nft.attributes.image}
                      fallback="data:image/png;base64,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"
                      alt=""
                      style={{ height: "190px" }}
                    />
                  }
                  key={index}
                >
                  <Meta
                    title={`Rank #${nft.attributes.rank}`}
                    description={`#${nft.attributes.tokenId}`}
                  />
                </Card>
              );
            })}
        </div>
        {NFTBalances && <Button onClick={() => addToNFTs(collection)} type="primary">Load More</Button>}
      </div>
    </>
  );
}

export default HomePage;

Yes itā€™s not generating the rarity column (as seen at 38:26 of the tutorial) - the rarity set code is in the script here:

newObject.set('rarity', nftArr[i].Rarity);

But the value of nftArr[i].Rarity is null.

At line 112 of your generator script or where let current = metadata[j]; is, if you change it to:

let current = metadata[j].attributes;

It seems to generate rarity - you will need to revert the QuickStart.jsx code back to {nft.attributes.map((e) => { as the above change has fixed the original issue with not using attributes correctly. You will also need to use a new table/class name in your script e.g. SoupTwo to test and change that in your QuickStart.jsx.

Iā€™m not sure if the numbers are right in terms of actual rarity (and some NFTs have the same rarity) - you can check that yourself.

1 Like

Thanks so much! I still have some collections that fails with a null attribute error, but then most others are generating rarity and working to push to the db. :+1: Here is most of the code

async function generateRarity() {
    await Moralis.start({ serverUrl, appId, moralisSecret });
    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));

    let cursor = null;

    do {
        const NFTs = await Moralis.Web3API.token.getAllTokenIds({
            address: collectionAddress,
            cursor: cursor,
            chain: "eth",
        });
        console.log(`Got page ${NFTs.page} of ${Math.ceil(NFTs.total / NFTs.page_size)}, ${NFTs.total} total`)
        allNFTs = allNFTs.concat(NFTs.result);
        await timer(9000);
        cursor = NFTs.cursor
    } while (cursor != '' && cursor != null)

    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);
    }

    let tally = { TraitCount: {} };

    for (let j = 0; j < metadata.length; j++) {
        let nftTraits = metadata[j].attributes.map((e) => e.trait_type);
        let nftValues = metadata[j].attributes.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].attributes;
        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));

        //let rarityScoreNumTraits =
        //    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) })

This the error I get after it goes through all the rarity and gets to the push parts it throws error TypeError: Cannot read properties of null (reading 'attributes') main.js:88

I didnā€™t get this error - you just need to debug this line where itā€™s trying to use attributes on a variable where attributes doesnā€™t exist - you can do some logging.

If this is for a different collection, it could be that one or more of the NFTs doesnā€™t have populated metadata or a working token_uri - you can check using the API.

1 Like

Yeah on a different collection NFTisse 0x343b68141129ec115c1fc523C5Ae90586fe95b77 the script logs out all the traits up until the last token in collection 3171, and then throws that attributes error. There seem to be quite a few duplicates also where it ranks the same token twice in a row. I ran it on another collection and have about 100 or more duplicates. I wonder if I could just delete the duplicates from the db. Thanks again for all your help, it be great to get this to a point it could be deployed.

Did you check that collection with the API for the metadata for each NFT? You can do a loop similar to this and then search for any null metadata.

As for duplicate results, there could be an error in the script (you could add logging as each object is saved and then look through them) or you can check the API to make sure thereā€™s no duplicate results from the API (sometimes happens).

1 Like