Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom Networks + Rarity Algorithms + Elrond Network config #1241

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
83de9e0
egld network created + egld output files in same folder + custom outp…
Apr 12, 2022
2c0fe73
terrain prepared. now we can calculate rarities
Apr 12, 2022
1b48d53
network customisation functionality, egld network config added, rarit…
Apr 14, 2022
0235a82
mediaFilePrefix customization and small bug fix
Apr 15, 2022
5e2be9f
easier network customization
Apr 15, 2022
a32d474
add Jaccard Distances rarity algorithm & add compatibility for existi…
Apr 16, 2022
3f2c932
existing scripts adapted for the new customisations
Apr 18, 2022
1b09f78
clean a little bit
Apr 18, 2022
8ae250a
rename TR algorithm to `Common`
Apr 18, 2022
a60fcf2
split TR & SR rarity algorithms; add rank for all rarity algorithms
Apr 19, 2022
68084fd
split constants files; split rarity & metadata into different compone…
Apr 25, 2022
ffba651
remove common.js; restructure project; improve some code
Apr 25, 2022
80cb604
move more functions into metadata.js component
Apr 25, 2022
7725e2c
set eth as default
Apr 26, 2022
bd25d43
improve code quality after PR review
Apr 28, 2022
a3490d3
fix typo & update README.md
Apr 28, 2022
9a4f518
Merge pull request #1 from johnykes/custom-networks-and-rarities
ccorcoveanu May 17, 2022
56ea29d
bug fix
johnykes Jun 29, 2022
04bfb82
code improvements (functions instead of strange code)
johnykes Jun 29, 2022
0b818cc
code improvements (markScoreAsUsed improvements)
johnykes Jun 29, 2022
813d575
Merge pull request #3 from ElrondNetwork/fix_jaccard_distances
johnykes Jun 29, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 44 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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
{
Expand Down Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions constants/metadata.js
Original file line number Diff line number Diff line change
@@ -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 };
42 changes: 37 additions & 5 deletions constants/network.js
Original file line number Diff line number Diff line change
@@ -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 };
9 changes: 9 additions & 0 deletions constants/rarity.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const RARITY = {
none: 0,
jaccardDistances: 1, // most accurate / recommended
traitRarity: 2,
statisticalRarity: 3,
traitAndStatisticalRarity: 4,
};

module.exports = { RARITY };
7 changes: 5 additions & 2 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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%
Expand All @@ -34,7 +37,7 @@ const layerConfigurations = [
{ name: "Bottom lid" },
{ name: "Top lid" },
],
},
}
];

const shuffleLayerConfigurations = false;
Expand Down
112 changes: 41 additions & 71 deletions src/main.js
Original file line number Diff line number Diff line change
@@ -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`);
Expand All @@ -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;
Expand All @@ -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) => {
Expand Down Expand Up @@ -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")
);
};
Expand All @@ -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({
Expand Down Expand Up @@ -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;
Expand All @@ -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++
) {
Expand All @@ -349,6 +301,7 @@ const startCreating = async () => {
if (shuffleLayerConfigurations) {
abstractedIndexes = shuffle(abstractedIndexes);
}
abstractedIndexesBackup = [...abstractedIndexes];
debugLogs
? console.log("Editions left to create: ", abstractedIndexes)
: null;
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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 };
Loading