Generate 879,120 cryptopunk NFTs with Javascript Nodejs command-line app (step by step)

CryptoPunks is one of the most popular NFT projects out there. And now, they are sold for millions of dollars. Yeah I know! Shocking! There are 10,000 uniquely punks. Each of them has a set of attributes which make them special and stand out from rest….


This content originally appeared on DEV Community and was authored by Victor Quan Lam

CryptoPunks is one of the most popular NFT projects out there. And now, they are sold for millions of dollars. Yeah I know! Shocking! There are 10,000 uniquely punks. Each of them has a set of attributes which make them special and stand out from rest.

Cryptopunk Types and Attributes

Punk Types

  1. Alien
  2. Ape
  3. Zombie
  4. Female
  5. Male

Attributes

There are approximately 89 attributes avail for each punk type.
punk attributes

Attribute Counts

Each punk can have none or up to 7 attributes at a time.

From the given materials, we can potentially create more than 800,000 cryptopunk nfts.

Let's put everything aside and write a little Javascript command-line app to generate a bunch of these punks. On top of that, we will get a chance to solve the "cartesian product of multiple arrays" challenge in Javascript.

Setting up

Download all the layers and punks image from this res.

Folder structure:
Folder structure

We will be using node-canvas package to draw image in this project. Please make sure that you the installation instruction if you run into problems. More helps can be found here.

npm install canvas

Add imports and config variables

const  fs = require("fs");

const { createCanvas, loadImage } = require("canvas");

const  console = require("console");

const  imageFormat = {

    width: 24,

    height: 24

};

// initialize canvas and context in 2d
const  canvas = createCanvas(imageFormat.width, imageFormat.height);

const  ctx = canvas.getContext("2d");

// some folder directories that we will use throughout the script
const  dir = {

    traitTypes  : `./layers/trait_types`,

    outputs: `./outputs`,

    background: `./layers/background`,

}

// we will update this total punks in the following steps.
let  totalOutputs = 0;

// set the order of layers that you want to print first
const  priorities = ['punks','top','beard'];

Refresh output folder function

  • A function helps you to remove the outputs and its data. Then it recreate the outputs folder along with new metadata and punk folders inside.
const  recreateOutputsDir = () => {

if (fs.existsSync(dir.outputs)) {

fs.rmdirSync(dir.outputs, { recursive: true });

}

fs.mkdirSync(dir.outputs);

fs.mkdirSync(`${dir.outputs}/metadata`);

fs.mkdirSync(`${dir.outputs}/punks`);

};

Calculate all the possible outcomes

In this step, we will figure out how to generate combinations from multiple arrays of trait layers. Now let's get down to business and have some fun. Don't copy and paste the code yet.

There're many ways to implement this so-called simple function.

  • Reduce and FlatMap functions which were introduced in ECMAScript 2019. This is the shortest option and yet easiest to understand.
const cartesian = (...a) => a.reduce((a, b) => a.flatMap(d => b.map(e => [d, e].flat())));
  • Another common option is to use Recursion function
const cartesian = (arr) => {
  if (arr.length == 1) {
    return arr[0];
  } else {
    var result = [];
    var allCasesOfRest = cartesian (arr.slice(1)); // recur with the rest of array
    for (var i = 0; i < allCasesOfRest.length; i++) {
      for (var j = 0; j < arr[0].length; j++) {
        var childArray = [].concat(arr[0][j], allCasesOfRest[i])
        result.push(childArray);
      }
    }
    return result;
  }
}

Most of the options require to have an absorb amount of recursion, or heavily nested loops or to store the array of permutations in memory. It will get really messy working with hundreds of different trait layers. These will use up all of your device's memory and eventually crash your PC/laptop. I got my PC fried multiple times. So don't be me.

  • Instead of using recursion function or nested loops, we can create a function to calculate the total possible outcomes which is the product of all array's length.
var permsCount = arraysToCombine[0].length;
for(var i = 1; i < arraysToCombine.length; i++) {
    permsCount  rms *= arraysToCombine[i].length;
}
  • Next, we will set the divisors value to solve the array size differ
for (var i = arraysToCombine.length - 1; i >= 0; i--) {
      divisors[i] = divisors[i + 1] ? divisors[i + 1] * arraysToCombine[i + 1].length : 1;
   }

Another function to return a unique permutation between index 0 and numPerms - 1 by calculating the indices it needs to retrieve its characters from, based on n

const getPermutation = (n, arraysToCombine) => {

    var  result = [],

    curArray;

    for (var  i = 0; i < arraysToCombine.length; i++) {

        curArray = arraysToCombine[i];

        result.push(curArray[Math.floor(n / divisors[i]) % curArray.length]);

    }

    return  result;
}

Next we will call getPermutation (n) function using for loop with permsCount as

    for(var i = 0; i < numPerms; i++) {
        combinations.push(getPermutation(i, arraysToCombine));
    }

The complete script that we need.

const  allPossibleCases = (arraysToCombine) => {

const  divisors = [];

let  permsCount = 1;

for (let  i = arraysToCombine.length - 1; i >= 0; i--) {

divisors[i] = divisors[i + 1] ? divisors[i + 1] * arraysToCombine[i + 1].length : 1;

permsCount *= (arraysToCombine[i].length || 1);

}


totalOutputs = permsCount;

  totalOutputs123-_

const  getCombination = (n, arrays, divisors) =>  arrays.reduce((acc, arr, i) => {

acc.push(arr[Math.floor(n / divisors[i]) % arr.length]);

return  acc;

}, []);



const  combinations = [];

for (let  i = 0; i < permsCount; i++) {

combinations.push(getCombination(i, arraysToCombine, divisors));

}

return  combinations;

};

According to this quick performance test, the last version completely outperform the others. Looks very promising to me.

performance test

Create draw image function

const  drawImage= async (traitTypes, background, index) => {

// draw background

const  backgroundIm = await  loadImage(`${dir.background}/${background}`);

ctx.drawImage(backgroundIm,0,0,imageFormat.width,imageFormat.height);



//'N/A': means that this punk doesn't have this trait type

const  drawableTraits = traitTypes.filter(x=>  x.value !== 'N/A')

// draw all the trait layers for this one punk

for (let  index = 0; index < drawableTraits.length; index++) {

    const  val = drawableTraits[index];

    const  image = await  loadImage(`${dir.traitTypes}/${val.trait_type}/${val.value}`);

    ctx.drawImage(image,0,0,imageFormat.width,imageFormat.height);

}



console.log(`Progress: ${index}/ ${totalOutputs}`)



// save metadata

fs.writeFileSync(

`${dir.outputs}/metadata/${index}.json`,

JSON.stringify({

name: `punk ${index}`,

attributes: drawableTraits

}),

function(err){

if(err) throw  err;

}

)



// save image as png file

fs.writeFileSync(

`${dir.outputs}/punks/${index}.png`,

canvas.toBuffer("image/png")

);

}

Create main function

const  main = async () => {

const  traitTypesDir = dir.traitTypes;

// register all the traits 

const  types = fs.readdirSync(traitTypesDir);

// set all priotized layers to be drawn first. for eg: punk type, top... You can set these values in the priorities array in line 21
const  traitTypes = priorities.concat(types.filter(x=> !priorities.includes(x)))

                .map(traitType  => (

                fs.readdirSync(`${traitTypesDir}/${traitType}/`)

                .map(value=> {

                return {trait_type: traitType, value: value}

                }).concat({trait_type: traitType, value: 'N/A'})

                ));


// register all the backgrounds
const  backgrounds = fs.readdirSync(dir.background);



// trait type avail for each punk

const  combinations = allPossibleCases(traitTypes)

for (var  n = 0; n < combinations.length; n++) {

    const  randomBackground = backgrounds[Math.floor(Math.random() * backgrounds.length)]

    await  drawImage(combinations[n] , randomBackground, n);

}

};

Call outputs directory register and main function

//main

(() => {

recreateOutputsDir();

main();

})();

Run index.js

Open cmd/powershell and run

node index.js

Or

npm build




Resources

  1. Source code: victorquanlam/cryptopunk-nft-generator)
  2. Stackoverflow: Cartesian product of array values

That's all. I hope that you enjoy the blog.


This content originally appeared on DEV Community and was authored by Victor Quan Lam


Print Share Comment Cite Upload Translate Updates
APA

Victor Quan Lam | Sciencx (2021-09-05T10:48:10+00:00) Generate 879,120 cryptopunk NFTs with Javascript Nodejs command-line app (step by step). Retrieved from https://www.scien.cx/2021/09/05/generate-879120-cryptopunk-nfts-with-javascript-nodejs-command-line-app-step-by-step/

MLA
" » Generate 879,120 cryptopunk NFTs with Javascript Nodejs command-line app (step by step)." Victor Quan Lam | Sciencx - Sunday September 5, 2021, https://www.scien.cx/2021/09/05/generate-879120-cryptopunk-nfts-with-javascript-nodejs-command-line-app-step-by-step/
HARVARD
Victor Quan Lam | Sciencx Sunday September 5, 2021 » Generate 879,120 cryptopunk NFTs with Javascript Nodejs command-line app (step by step)., viewed ,<https://www.scien.cx/2021/09/05/generate-879120-cryptopunk-nfts-with-javascript-nodejs-command-line-app-step-by-step/>
VANCOUVER
Victor Quan Lam | Sciencx - » Generate 879,120 cryptopunk NFTs with Javascript Nodejs command-line app (step by step). [Internet]. [Accessed ]. Available from: https://www.scien.cx/2021/09/05/generate-879120-cryptopunk-nfts-with-javascript-nodejs-command-line-app-step-by-step/
CHICAGO
" » Generate 879,120 cryptopunk NFTs with Javascript Nodejs command-line app (step by step)." Victor Quan Lam | Sciencx - Accessed . https://www.scien.cx/2021/09/05/generate-879120-cryptopunk-nfts-with-javascript-nodejs-command-line-app-step-by-step/
IEEE
" » Generate 879,120 cryptopunk NFTs with Javascript Nodejs command-line app (step by step)." Victor Quan Lam | Sciencx [Online]. Available: https://www.scien.cx/2021/09/05/generate-879120-cryptopunk-nfts-with-javascript-nodejs-command-line-app-step-by-step/. [Accessed: ]
rf:citation
» Generate 879,120 cryptopunk NFTs with Javascript Nodejs command-line app (step by step) | Victor Quan Lam | Sciencx | https://www.scien.cx/2021/09/05/generate-879120-cryptopunk-nfts-with-javascript-nodejs-command-line-app-step-by-step/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.