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