diff --git a/README.md b/README.md index 98a393c2e..76d442639 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ If you want to have logs to debug and see what is happening when you generate im If you want to play around with different blending modes, you can add a `blend: MODE.colorBurn` field to the layersOrder `options` object. -If you need a layers to have a different opacity then you can add the `opacity: 0.7` field to the layersOrder `options` object as well. +If you need a layer to have a different opacity then you can add the `opacity: 0.7` field to the layersOrder `options` object as well. If you want to have a layer _ignored_ in the DNA uniqueness check, you can set `bypassDNA: true` in the `options` object. This has the effect of making sure the rest of the traits are unique while not considering the `Background` Layers as traits, for example. The layers _are_ included in the final image. @@ -190,7 +190,7 @@ or node index.js ``` -The program will output all the images in the `build/images` directory along with the metadata files in the `build/json` directory. Each collection will have a `_metadata.json` file that consists of all the metadata in the collection inside the `build/json` directory. The `build/json` folder also will contain all the single json files that represent each image file. The single json file of a image will look something like this: +The program will output all the images in the `build/media` directory along with the metadata files in the `build/json` directory. Each collection will have a `_metadata.json` file that consists of all the metadata in the collection inside the `build/json` directory. The `build/json` folder also will contain all the single json files that represent each image file. The single json file of a image will look something like this: ```json { @@ -229,6 +229,48 @@ const extraMetadata = {}; That's it, you're done. +## Output customization +Depending on the minting process / marketplace you choose, you will need to respect an output folder structure or you may want to include rarity metadata.\ +In order to get your desired output structure / rarity metadata, you can choose from the already created networks/standards that can be found in `network.js` and update the network value in `config.js` +``` +// config.js +const network = NETWORK.egld; +``` +or you can create your own network/standard in `network.js`. +``` +// network.js +const NETWORK = { + egld: { + name: "egld", + startIdx: 1, + metadataFileName: "_metadata.json", + metadataType: METADATA.rarities, + rarityAlgorithm: RARITY.jaccardDistances, + includeRank: true + }, + ... +} +``` +The `metadataType` and `rarityAlgorithm` options can be found in `constants/metadata.js` and `constants/rarity.js`. +``` +const METADATA = { + // metadata file will contain all individual metadata files (common for eth, sol) + // no rarities at all + basic: 0, + // metadata file will contain only rarity data for traits & attributes (common for egld$) + // if rarityAlgorithm provided, individual metadata files will also contain rarity data + rarities: 1, +}; + +const RARITY = { + none: 0, + jaccardDistances: 1, // most accurate / recommended + traitRarity: 2, + statisticalRarity: 3, + traitAndStatisticalRarity: 4, +}; +``` + ## Utils ### Updating baseUri for IPFS and description diff --git a/constants/metadata.js b/constants/metadata.js new file mode 100644 index 000000000..b517fc968 --- /dev/null +++ b/constants/metadata.js @@ -0,0 +1,10 @@ +const METADATA = { + // metadata file will contain all individual metadata files (common for eth, sol) + // no rarities at all + basic: 0, + // metadata file will contain only rarity data for traits & attributes (common for egld) + // if rarityAlgorithm provided, individual metadata files will also contain rarity data + rarities: 1, +}; + +module.exports = { METADATA }; diff --git a/constants/network.js b/constants/network.js index bd962822e..635af4fe5 100644 --- a/constants/network.js +++ b/constants/network.js @@ -1,8 +1,40 @@ -const NETWORK = { - eth: "eth", - sol: "sol", +const { METADATA } = require("../constants/metadata"); +const { RARITY } = require("../constants/rarity"); + +const defaults = { + startIdx: 1, + jsonDirPrefix: "", + mediaDirPrefix: "", + mediaFilePrefix: "", + metadataFileName: "_metadata.json", + metadataType: METADATA.basic, + rarityAlgorithm: RARITY.none, + includeRank: false, }; -module.exports = { - NETWORK, +const NETWORK = { + eth: { + ...defaults, + name: "eth", + jsonDirPrefix: "json/", + mediaDirPrefix: "media/", + mediaFilePrefix: "$", + }, + sol: { + ...defaults, + name: "sol", + startIdx: 0, + jsonDirPrefix: "json/", + mediaDirPrefix: "media/", + mediaFilePrefix: "$", + }, + egld: { + ...defaults, + name: "egld", + metadataType: METADATA.rarities, + rarityAlgorithm: RARITY.jaccardDistances, + includeRank: true, + }, }; + +module.exports = { NETWORK }; diff --git a/constants/rarity.js b/constants/rarity.js new file mode 100644 index 000000000..f76967467 --- /dev/null +++ b/constants/rarity.js @@ -0,0 +1,9 @@ +const RARITY = { + none: 0, + jaccardDistances: 1, // most accurate / recommended + traitRarity: 2, + statisticalRarity: 3, + traitAndStatisticalRarity: 4, +}; + +module.exports = { RARITY }; diff --git a/src/config.js b/src/config.js index c46d867a0..adb365d4a 100644 --- a/src/config.js +++ b/src/config.js @@ -4,11 +4,14 @@ const { NETWORK } = require(`${basePath}/constants/network.js`); const network = NETWORK.eth; -// General metadata for Ethereum +// General metadata const namePrefix = "Your Collection"; const description = "Remember to replace this description"; + +// Ethereum metadata const baseUri = "ipfs://NewUriToReplace"; +// Solana metadata const solanaMetadata = { symbol: "YC", seller_fee_basis_points: 1000, // Define how much % you want from secondary market sales 1000 = 10% @@ -34,7 +37,7 @@ const layerConfigurations = [ { name: "Bottom lid" }, { name: "Top lid" }, ], - }, + } ]; const shuffleLayerConfigurations = false; diff --git a/src/main.js b/src/main.js index e9c08dcf2..361e261df 100644 --- a/src/main.js +++ b/src/main.js @@ -1,5 +1,7 @@ const basePath = process.cwd(); const { NETWORK } = require(`${basePath}/constants/network.js`); +const { METADATA } = require(`${basePath}/constants/metadata.js`); +const { RARITY } = require(`${basePath}/constants/rarity.js`); const fs = require("fs"); const sha1 = require(`${basePath}/node_modules/sha1`); const { createCanvas, loadImage } = require(`${basePath}/node_modules/canvas`); @@ -22,6 +24,15 @@ const { solanaMetadata, gif, } = require(`${basePath}/src/config.js`); +const { + createMetadataItem, + writeMetadataFile, + saveIndividualMetadataFiles, +} = require(`${basePath}/src/metadata.js`); +const { + getGeneralRarity, + getItemsRarity, +} = require(`${basePath}/src/rarity.js`); const canvas = createCanvas(format.width, format.height); const ctx = canvas.getContext("2d"); ctx.imageSmoothingEnabled = format.smoothing; @@ -38,11 +49,11 @@ const buildSetup = () => { fs.rmdirSync(buildDir, { recursive: true }); } fs.mkdirSync(buildDir); - fs.mkdirSync(`${buildDir}/json`); - fs.mkdirSync(`${buildDir}/images`); - if (gif.export) { - fs.mkdirSync(`${buildDir}/gifs`); - } + + if (network.jsonDirPrefix) + fs.mkdirSync(`${buildDir}/${network.jsonDirPrefix}`); + if (network.mediaDirPrefix) + fs.mkdirSync(`${buildDir}/${network.mediaDirPrefix}`); }; const getRarityWeight = (_str) => { @@ -112,7 +123,7 @@ const layersSetup = (layersOrder) => { const saveImage = (_editionCount) => { fs.writeFileSync( - `${buildDir}/images/${_editionCount}.png`, + `${buildDir}/${network.mediaDirPrefix}${network.mediaFilePrefix}${_editionCount}.png`, canvas.toBuffer("image/png") ); }; @@ -128,49 +139,6 @@ const drawBackground = () => { ctx.fillRect(0, 0, format.width, format.height); }; -const addMetadata = (_dna, _edition) => { - let dateTime = Date.now(); - let tempMetadata = { - name: `${namePrefix} #${_edition}`, - description: description, - image: `${baseUri}/${_edition}.png`, - dna: sha1(_dna), - edition: _edition, - date: dateTime, - ...extraMetadata, - attributes: attributesList, - compiler: "HashLips Art Engine", - }; - if (network == NETWORK.sol) { - tempMetadata = { - //Added metadata for solana - name: tempMetadata.name, - symbol: solanaMetadata.symbol, - description: tempMetadata.description, - //Added metadata for solana - seller_fee_basis_points: solanaMetadata.seller_fee_basis_points, - image: `${_edition}.png`, - //Added metadata for solana - external_url: solanaMetadata.external_url, - edition: _edition, - ...extraMetadata, - attributes: tempMetadata.attributes, - properties: { - files: [ - { - uri: `${_edition}.png`, - type: "image/png", - }, - ], - category: "image", - creators: solanaMetadata.creators, - }, - }; - } - metadataList.push(tempMetadata); - attributesList = []; -}; - const addAttributes = (_element) => { let selectedElement = _element.layer.selectedElement; attributesList.push({ @@ -303,23 +271,6 @@ const createDna = (_layers) => { return randNum.join(DNA_DELIMITER); }; -const writeMetaData = (_data) => { - fs.writeFileSync(`${buildDir}/json/_metadata.json`, _data); -}; - -const saveMetaDataSingleFile = (_editionCount) => { - let metadata = metadataList.find((meta) => meta.edition == _editionCount); - debugLogs - ? console.log( - `Writing metadata for ${_editionCount}: ${JSON.stringify(metadata)}` - ) - : null; - fs.writeFileSync( - `${buildDir}/json/${_editionCount}.json`, - JSON.stringify(metadata, null, 2) - ); -}; - function shuffle(array) { let currentIndex = array.length, randomIndex; @@ -339,8 +290,9 @@ const startCreating = async () => { let editionCount = 1; let failedCount = 0; let abstractedIndexes = []; + let abstractedIndexesBackup = []; for ( - let i = network == NETWORK.sol ? 0 : 1; + let i = network.startIdx; i <= layerConfigurations[layerConfigurations.length - 1].growEditionSizeTo; i++ ) { @@ -349,6 +301,7 @@ const startCreating = async () => { if (shuffleLayerConfigurations) { abstractedIndexes = shuffle(abstractedIndexes); } + abstractedIndexesBackup = [...abstractedIndexes]; debugLogs ? console.log("Editions left to create: ", abstractedIndexes) : null; @@ -375,7 +328,7 @@ const startCreating = async () => { hashlipsGiffer = new HashlipsGiffer( canvas, ctx, - `${buildDir}/gifs/${abstractedIndexes[0]}.gif`, + `${buildDir}/${network.mediaDirPrefix}${abstractedIndexes[0]}.gif`, gif.repeat, gif.quality, gif.delay @@ -402,8 +355,10 @@ const startCreating = async () => { ? console.log("Editions left to create: ", abstractedIndexes) : null; saveImage(abstractedIndexes[0]); - addMetadata(newDna, abstractedIndexes[0]); - saveMetaDataSingleFile(abstractedIndexes[0]); + metadataList.push( + createMetadataItem(attributesList, newDna, abstractedIndexes[0]) + ); + attributesList = []; console.log( `Created edition: ${abstractedIndexes[0]}, with DNA: ${sha1( newDna @@ -426,7 +381,22 @@ const startCreating = async () => { } layerConfigIndex++; } - writeMetaData(JSON.stringify(metadataList, null, 2)); + + // calculate rarities (if needed) & save _metadata.json file + if (network.metadataType === METADATA.basic) { + writeMetadataFile(JSON.stringify(metadataList, null, 2)); + } else if (network.metadataType === METADATA.rarities) { + // calculate rarity for traits/layers & attributes/assets + const rarityObject = getGeneralRarity(metadataList); + if (network.rarityAlgorithm !== RARITY.none) { + // calculate rarity for all items/NFTs + metadataList = getItemsRarity(metadataList, rarityObject); + } + writeMetadataFile(JSON.stringify(rarityObject, null, 2)); + } + + // save individual metadata files + saveIndividualMetadataFiles(metadataList, abstractedIndexesBackup); }; module.exports = { startCreating, buildSetup, getElements }; diff --git a/src/metadata.js b/src/metadata.js new file mode 100644 index 000000000..edcd870cb --- /dev/null +++ b/src/metadata.js @@ -0,0 +1,129 @@ +const basePath = process.cwd(); +const buildDir = `${basePath}/build`; +const { NETWORK } = require(`${basePath}/constants/network.js`); +const { METADATA } = require(`${basePath}/constants/metadata.js`); +const fs = require("fs"); +const sha1 = require(`${basePath}/node_modules/sha1`); +const { + baseUri, + description, + extraMetadata, + namePrefix, + network, + solanaMetadata, + debugLogs, +} = require(`${basePath}/src/config.js`); + +// create metadata item with the provided data +const createMetadataItem = (attributesList, _dna, _edition) => { + let tempMetadata = {}; + + switch (network) { + case NETWORK.egld: { + tempMetadata = { + description: description, + dna: sha1(_dna), + ...extraMetadata, + attributes: attributesList, + rarities: {}, + compiler: "HashLips Art Engine", + }; + return tempMetadata; + } + case NETWORK.eth: { + tempMetadata = { + name: `${namePrefix} #${_edition}`, + description: description, + image: `${baseUri}/${_edition}.png`, + dna: sha1(_dna), + edition: _edition, + date: Date.now(), + attributes: attributesList, + ...extraMetadata, + compiler: "HashLips Art Engine", + }; + return tempMetadata; + } + case NETWORK.sol: { + tempMetadata = { + name: `${namePrefix} #${_edition}`, + symbol: solanaMetadata.symbol, + description: description, + seller_fee_basis_points: solanaMetadata.seller_fee_basis_points, + image: `${_edition}.png`, + external_url: solanaMetadata.external_url, + edition: _edition, + ...extraMetadata, + attributes: attributesList, + properties: { + files: [ + { + uri: `${_edition}.png`, + type: "image/png", + }, + ], + category: "image", + creators: solanaMetadata.creators, + }, + }; + return tempMetadata; + } + default: { + return tempMetadata; + } + } +}; + +// get metadata of all generated NFTs/items +const getMetadataItems = () => { + if (network.metadataType === METADATA.basic) { + // get metadata from the _metadata.json file + return JSON.parse( + fs.readFileSync( + `${basePath}/build/${network.jsonDirPrefix}${network.metadataFileName}` + ) + ); + } + // get metadata from the individual metadata files + const jsonFilePattern = /^\d+.(json)$/i; + const files = fs.readdirSync(`${basePath}/build/${network.jsonDirPrefix}`); + return files + .filter((file) => file.match(jsonFilePattern)) + .map((file) => + JSON.parse( + fs.readFileSync(`${basePath}/build/${network.jsonDirPrefix}${file}`) + ) + ); +}; + +const writeMetadataFile = (_data) => { + fs.writeFileSync( + `${buildDir}/${network.jsonDirPrefix}${network.metadataFileName}`, + _data + ); +}; + +const saveIndividualMetadataFiles = (metadataList, abstractedIndexes) => { + metadataList.forEach((item, index) => { + if (debugLogs) { + console.log( + `Writing metadata for ${ + item.edition || abstractedIndexes[index] + }: ${JSON.stringify(item)}` + ); + } + fs.writeFileSync( + `${buildDir}/${network.jsonDirPrefix}${ + item.edition || abstractedIndexes[index] + }.json`, + JSON.stringify(item, null, 2) + ); + }); +}; + +module.exports = { + createMetadataItem, + getMetadataItems, + writeMetadataFile, + saveIndividualMetadataFiles, +}; diff --git a/src/rarity.js b/src/rarity.js new file mode 100644 index 000000000..a20045195 --- /dev/null +++ b/src/rarity.js @@ -0,0 +1,257 @@ +const basePath = process.cwd(); +const { RARITY } = require(`${basePath}/constants/rarity.js`); +const { network } = require(`${basePath}/src/config.js`); + +// calculates rarity for each trait/layer & attribute/asset +const getGeneralRarity = (metadataList) => { + let rarityObject = {}; + let traitOccurances = []; + let totalAttributesCnt = 0; + + // count occurrences for all traits/layers & attributes/assets + metadataList.forEach((item) => { + item.attributes.forEach((a) => { + if (rarityObject[a.trait_type] != null) { + if (rarityObject[a.trait_type][a.value] != null) { + rarityObject[a.trait_type][a.value].attributeOccurrence++; + } else { + rarityObject[a.trait_type][a.value] = { attributeOccurrence: 1 }; + totalAttributesCnt++; + } + + traitOccurances[a.trait_type]++; + } else { + rarityObject[a.trait_type] = { [a.value]: { attributeOccurrence: 1 } }; + traitOccurances[a.trait_type] = 1; + totalAttributesCnt++; + } + }); + }); + + // general rarity metadata for all traits/layers & attributes/assets + Object.entries(rarityObject).forEach((entry) => { + const layer = entry[0]; + const assets = entry[1]; + + Object.entries(assets).forEach((asset) => { + // trait/layer related + asset[1].traitOccurance = traitOccurances[layer]; + asset[1].traitOccurancePercentage = + (metadataList.length / asset[1].traitOccurance) * 100; + + // attribute/asset related + asset[1].attributeOccurrencePercentage = + (asset[1].attributeOccurrence / metadataList.length) * 100; + + // TR / SR algorithms specific metadata + if ( + network.rarityAlgorithm === RARITY.traitRarity || + network.rarityAlgorithm === RARITY.statisticalRarity || + network.rarityAlgorithm === RARITY.traitAndStatisticalRarity + ) { + // logic from https://github.com/xterr/nft-generator/blob/d8992d2bcfa729a6b2ef443f9404ffa28102111b/src/components/RarityResolver.ts + // ps: the only difference being that attributeRarityNormed is calculated only once + const totalLayersCnt = Object.keys(traitOccurances).length; + const avgAttributesPerTrait = totalAttributesCnt / totalLayersCnt; + asset[1].attributeFrequency = + asset[1].attributeOccurrence / metadataList.length; + asset[1].traitFrequency = asset[1].traitOccurance > 0 ? 1 : 0; + asset[1].attributeRarity = + metadataList.length / asset[1].attributeOccurrence; + asset[1].attributeRarityNormed = + asset[1].attributeRarity * (avgAttributesPerTrait / totalLayersCnt); + } + }); + }); + + return rarityObject; +}; + +// calculates rarity for all items/NFT +const getItemsRarity = (metadataList, rarityObject) => { + switch (network.rarityAlgorithm) { + case RARITY.none: { + return; + } + case RARITY.jaccardDistances: { + return getItemsRarity_jaccardDistances(metadataList); + } + case RARITY.traitRarity: + case RARITY.statisticalRarity: + case RARITY.traitAndStatisticalRarity: { + return getItemsRarity_TSR(metadataList, rarityObject); + } + default: + break; + } +}; + +// calculates rarity for all items/NFT using Jaccard Distances algorithm +const getItemsRarity_jaccardDistances = (metadataList) => { + let z = []; + let avg = []; + + // calculate z(i,j) and avg(i) + for (let i = 0; i < metadataList.length; i++) { + for (let j = 0; j < metadataList.length; j++) { + if (i == j) continue; + + if (z[i] == null) { + z[i] = []; + } + + if (z[i][j] == null || z[j][i] == null) { + const commonTraitsCnt = getObjectCommonCnt( + metadataList[i].attributes, + metadataList[j].attributes + ); + const uniqueTraitsCnt = getObjectUniqueCnt( + metadataList[i].attributes, + metadataList[j].attributes + ); + + z[i][j] = commonTraitsCnt / uniqueTraitsCnt; + } + } + + // ps: length-1 because there's always an empty cell in matrix, where i == j + avg[i] = z[i].reduce((a, b) => a + b, 0) / (z[i].length - 1); + } + + // calculate z(i) + let jd = []; + let avgMax = Math.max(...avg); + let avgMin = Math.min(...avg); + + for (let i = 0; i < metadataList.length; i++) { + jd[i] = ((avg[i] - avgMin) / (avgMax - avgMin)) * 100; + } + + let jd_asc = [...jd].sort(function (a, b) { + return a - b; + }); + + // add JD rarity data to NFT/item + for (let i = 0; i < metadataList.length; i++) { + let scoreIndex = getScoreIndex(jd_asc, jd[i]); + jd_asc = markScoreAsUsed(jd_asc, scoreIndex); + + metadataList[i].rarity = { + score: jd[i], + }; + if (network.includeRank) { + metadataList[i].rarity.rank = jd.length - scoreIndex; + } + } + return metadataList; +}; + +const getScoreIndex = (jd_asc, score) => { + return jd_asc.indexOf(score); +}; + +const markScoreAsUsed = (jd_asc, scoreIndex) => { + jd_asc[scoreIndex] = -1; + return jd_asc; +}; + +// calculates rarity for all items/NFT using Trait/Statistical rarity algorithm(s) +const getItemsRarity_TSR = (metadataList, rarityObject) => { + metadataList.forEach((item) => { + item.rarity = { + usedTraitsCount: item.attributes.length, + }; + // TR specific + if ( + network.rarityAlgorithm === RARITY.traitRarity || + network.rarityAlgorithm === RARITY.traitAndStatisticalRarity + ) { + item.rarity.avgRarity = 0; + item.rarity.rarityScore = 0; + item.rarity.rarityScoreNormed = 0; + } + // SR specific + if ( + network.rarityAlgorithm === RARITY.statisticalRarity || + network.rarityAlgorithm === RARITY.traitAndStatisticalRarity + ) { + item.rarity.statRarity = 1; + } + + item.attributes.forEach((a) => { + const attributeData = rarityObject[a.trait_type][a.value]; + if ( + network.rarityAlgorithm === RARITY.traitRarity || + network.rarityAlgorithm === RARITY.traitAndStatisticalRarity + ) { + item.rarity.avgRarity += attributeData.attributeFrequency; + item.rarity.rarityScore += attributeData.attributeRarity; + item.rarity.rarityScoreNormed += attributeData.attributeRarityNormed; + } + // SR specific + if ( + network.rarityAlgorithm === RARITY.statisticalRarity || + network.rarityAlgorithm === RARITY.traitAndStatisticalRarity + ) { + item.rarity.statRarity *= attributeData.attributeFrequency; + } + }); + }); + + // add rarity rank + if (network.includeRank) { + const metadataList_asc = [...metadataList].sort(function (a, b) { + return ( + a.rarity.rarityScore - b.rarity.rarityScore ?? + b.rarity.statRarity - a.rarity.statRarity + ); + }); + for (let i = 0; i < metadataList.length; i++) { + metadataList[i].rarity.rank = + metadataList.length - metadataList_asc.indexOf(metadataList[i]); + } + } + + return metadataList; +}; + +// get common elements counter of 2 arrays +const getArrayCommonCnt = (arr1, arr2) => { + return arr1.filter((e) => { + return arr2.includes(e); + }).length; +}; + +// get unique elements counter of 2 arrays +const getArrayUniqueCnt = (arr1, arr2) => { + return [...new Set(arr1.concat(arr2))].length; +}; + +// get common elements counter of 2 objects +const getObjectCommonCnt = (obj1, obj2) => { + let arr1 = []; + let arr2 = []; + for (const [key, value] of Object.entries(obj1)) { + arr1.push(JSON.stringify(value)); + } + for (const [key, value] of Object.entries(obj2)) { + arr2.push(JSON.stringify(value)); + } + + return getArrayCommonCnt(arr1, arr2); +}; + +// get unique elements counter of 2 objects +const getObjectUniqueCnt = (obj1, obj2) => { + let arr1 = []; + let arr2 = []; + for (const [key, value] of Object.entries(obj1)) { + arr1.push(JSON.stringify(value)); + } + for (const [key, value] of Object.entries(obj2)) { + arr2.push(JSON.stringify(value)); + } + return getArrayUniqueCnt(arr1, arr2); +}; + +module.exports = { getGeneralRarity, getItemsRarity }; diff --git a/utils/generate_metadata.js b/utils/generate_metadata.js index 21d0a7ca9..b50c0e23d 100644 --- a/utils/generate_metadata.js +++ b/utils/generate_metadata.js @@ -2,8 +2,9 @@ const fs = require("fs"); const path = require("path"); const { createCanvas, loadImage } = require("canvas"); const basePath = process.cwd(); -const buildDir = `${basePath}/build/json`; -const inputDir = `${basePath}/build/images`; +const { network } = require(`${basePath}/src/config.js`); +const buildDir = `${basePath}/build/${network.jsonDirPath}`; +const inputDir = `${basePath}/build/${network.jsonDirPath}`; const { format, namePrefix, @@ -153,7 +154,7 @@ const saveMetadata = (_loadedImageObject) => { }; const writeMetaData = (_data) => { - fs.writeFileSync(`${buildDir}/_metadata.json`, _data); + fs.writeFileSync(`${buildDir}/${network.metadataFileName}`, _data); }; const startCreating = async () => { diff --git a/utils/pixelate.js b/utils/pixelate.js index 5fa3be377..29178085b 100644 --- a/utils/pixelate.js +++ b/utils/pixelate.js @@ -2,9 +2,9 @@ const fs = require("fs"); const path = require("path"); const { createCanvas, loadImage } = require("canvas"); const basePath = process.cwd(); +const { format, network, pixelFormat } = require(`${basePath}/src/config.js`); const buildDir = `${basePath}/build/pixel_images`; -const inputDir = `${basePath}/build/images`; -const { format, pixelFormat } = require(`${basePath}/src/config.js`); +const inputDir = `${basePath}/build/${network.mediaDirPrefix}`; const console = require("console"); const canvas = createCanvas(format.width, format.height); const ctx = canvas.getContext("2d"); diff --git a/utils/preview.js b/utils/preview.js index 5765e6c81..fafce19da 100644 --- a/utils/preview.js +++ b/utils/preview.js @@ -3,11 +3,11 @@ const fs = require("fs"); const { createCanvas, loadImage } = require("canvas"); const buildDir = `${basePath}/build`; -const { preview } = require(`${basePath}/src/config.js`); +const { network, preview } = require(`${basePath}/src/config.js`); +const { getMetadataItems } = require(`${basePath}/src/metadata.js`); // read json data -const rawdata = fs.readFileSync(`${basePath}/build/json/_metadata.json`); -const metadataList = JSON.parse(rawdata); +const metadataList = getMetadataItems(); const saveProjectPreviewImage = async (_data) => { // Extract from preview config @@ -32,7 +32,11 @@ const saveProjectPreviewImage = async (_data) => { // Don't want to rely on "edition" for assuming index for (let index = 0; index < _data.length; index++) { const nft = _data[index]; - await loadImage(`${buildDir}/images/${nft.edition}.png`).then((image) => { + await loadImage( + `${buildDir}/${network.mediaDirPrefix}${ + network.mediaFilePrefix + }${nft.edition}.png` + ).then((image) => { previewCtx.drawImage( image, thumbWidth * (index % thumbPerRow), diff --git a/utils/preview_gif.js b/utils/preview_gif.js index 0b0c339ba..d52a0535a 100644 --- a/utils/preview_gif.js +++ b/utils/preview_gif.js @@ -2,7 +2,7 @@ const basePath = process.cwd(); const fs = require("fs"); const { createCanvas, loadImage } = require("canvas"); const buildDir = `${basePath}/build`; -const imageDir = `${buildDir}/images`; +const imageDir = `${buildDir}/${network.mediaDirPrefix}`; const { format, preview_gif } = require(`${basePath}/src/config.js`); const canvas = createCanvas(format.width, format.height); const ctx = canvas.getContext("2d"); diff --git a/utils/rarity.js b/utils/rarity.js index ac577aaf3..361e553ae 100644 --- a/utils/rarity.js +++ b/utils/rarity.js @@ -1,19 +1,23 @@ const basePath = process.cwd(); const fs = require("fs"); +const { exit } = require("process"); +const { METADATA } = require(`${basePath}/constants/metadata.js`); +const { network } = require(`${basePath}/src/config.js`); const layersDir = `${basePath}/layers`; const { layerConfigurations } = require(`${basePath}/src/config.js`); +const { getMetadataItems } = require(`${basePath}/src/metadata.js`); -const { getElements } = require("../src/main.js"); +const { getElements } = require(`${basePath}/src/main.js`); // read json data -let rawdata = fs.readFileSync(`${basePath}/build/json/_metadata.json`); -let data = JSON.parse(rawdata); +let data = getMetadataItems(); let editionSize = data.length; let rarityData = []; // intialize layers to chart +console.log("layerconfig", layerConfigurations); layerConfigurations.forEach((config) => { let layers = config.layersOrder; @@ -59,16 +63,19 @@ data.forEach((element) => { }); }); -// convert occurrences to occurence string +// convert occurrences to occurrences string for (var layer in rarityData) { for (var attribute in rarityData[layer]) { // get chance - let chance = - ((rarityData[layer][attribute].occurrence / editionSize) * 100).toFixed(2); + let chance = ( + (rarityData[layer][attribute].occurrence / editionSize) * + 100 + ).toFixed(2); // show two decimal places in percent - rarityData[layer][attribute].occurrence = - `${rarityData[layer][attribute].occurrence} in ${editionSize} editions (${chance} %)`; + rarityData[layer][ + attribute + ].occurrence = `${rarityData[layer][attribute].occurrence} in ${editionSize} editions (${chance} %)`; } } diff --git a/utils/update_info.js b/utils/update_info.js index f51788631..71bdc36fb 100644 --- a/utils/update_info.js +++ b/utils/update_info.js @@ -1,6 +1,8 @@ const basePath = process.cwd(); const { NETWORK } = require(`${basePath}/constants/network.js`); const fs = require("fs"); +const { METADATA } = require(`${basePath}/constants/metadata`); +const { getMetadataItems } = require(`${basePath}/src/metadata.js`); const { baseUri, @@ -11,31 +13,42 @@ const { } = require(`${basePath}/src/config.js`); // read json data -let rawdata = fs.readFileSync(`${basePath}/build/json/_metadata.json`); -let data = JSON.parse(rawdata); +let data = getMetadataItems(); +let idx = network.startIdx; data.forEach((item) => { - if (network == NETWORK.sol) { - item.name = `${namePrefix} #${item.edition}`; - item.description = description; - item.creators = solanaMetadata.creators; + // general metadata + item.description = description; + if (network.metadataType != METADATA.basic) { + item.name = `${namePrefix} #${idx++}`; } else { item.name = `${namePrefix} #${item.edition}`; - item.description = description; + } + + // custom metadata + if (network === NETWORK.eth) { item.image = `${baseUri}/${item.edition}.png`; } + if (network === NETWORK.sol) { + item.creators = solanaMetadata.creators; + } + fs.writeFileSync( - `${basePath}/build/json/${item.edition}.json`, + `${basePath}/build/${network.jsonDirPrefix}${item.edition}.json`, JSON.stringify(item, null, 2) ); }); -fs.writeFileSync( - `${basePath}/build/json/_metadata.json`, - JSON.stringify(data, null, 2) -); +if (network.metadataType === METADATA.basic) { + fs.writeFileSync( + `${basePath}/build/${network.jsonDirPrefix}${ + network.metadataFileName + }`, + JSON.stringify(data, null, 2) + ); +} -if (network == NETWORK.sol) { +if (network === NETWORK.sol) { console.log(`Updated description for images to ===> ${description}`); console.log(`Updated name prefix for images to ===> ${namePrefix}`); console.log(