Creating Generative Art Ordinals with JavaScript

Zachary Weiner
5 min readJul 9, 2023

--

Layers

Generative art is by far the leader in the world of NFTs. But, publishing thousands of images can be very expensive. To make it easier we’re going to build a project that uses ordinals as layers to build the picture instead of uploading each unique picture. This way we can upload the image layers once, and then each NFT ordinal will be HTML & JavaScript text to bring down the cost of minting.

The unique part? All the randomness for making the layers random comes from Bitcoin itself, not from the designers, developers or platforms!

Before we get started checkout this example what were building:

Setting Up The Webpage

First, we’ll make a simple webpage. We add a canvas to our page — this is where we will put our picture.

<!DOCTYPE html>
<html>
<head>
<title>Artistic Layers</title>
</head>
<body>
<h1>Welcome to the Artistic Layers</h1>
<canvas id="canvas"></canvas>
<script>
<!-- We'll put our JavaScript here -->
<!-- You can copy and paste each snippet in, just make sure to paste each new bit of code below the last -->
</script>
</body>
</html>

Image Layers

Now we need some images to work with. We have to organize our pictures into groups, which we call layers. Each layer can have several different images, but only one image from each layer will be used in the final picture.

const layers = {
'B': ['743ae1606dd64d0f9c5a0d9a003672baf2480c39c58d8b790e79e993a22e5d23_0', '18c001e938c871550b1f48ce89d3816e7a2daeb9e2d782645a4b7c742334af95_0', '8a3d06306cc95bde9caa417b401988fcc10871d5e2a1e785a9a188d4f7649e8c_0', '58ad4d9b2a6d1fe1e63e64dca32a3c024c9c14da9dcb50d38a0d3a1eead8b12c_0', '26534738800f8222f654caa7f3029f9b35a6203b3048a1a1cec7358789f4de27_0', 'c1481acfc865bdca4717b235fad9af94e242004fe6773176610374cb9f6232fc_0', '67a41a5a378056385bcbaf1d0379f308fb5e0dddda62732acca1b003d72dd6c3_0'],
'I1': ['b3c68338044b73d6abb9b3863569ce78153f156874811dca2a1c9ff9e4964638_0', '48445b7dbd7537e3e8dbbe1d10f112022657436f68b8b11cc8b40bdfe41436f4_0', 'b8cec291abbc9f0abf1442ce03f60ce8b2ef60cf6290cf22e76ca584c2e0f323_0', 'ebfdc96141ad2be06f58101d0d6dd5b1b24c8219e65ab01221970b9804c44943_0', '24fc0ee3a33e4e3d676d79e5e329ee977b2fa9b6e1f7ebe6551d007c2d6bbda7_0', '83e75f9379f8ee9caa493b1cd0b44ef74d2fc0986f7f5f2c785343eda810cd4d_0', '76828ee9aa2880ed426378d92a8b049c626d31ae9021f6fccc9018c01561cd60_0'],
'T': ['4f6697de637839d8b5208beecfdd373ec229b2f07c21e7dbfe0aa89edb167122_0', 'bfbd139c9bdd6eea181e797c95f487b50bcb84c58d2d95601678f39e4b611f17_0', 'c9ed16727de97ffa06a9824a49abd1c444ec2eae6463d703872b00ca049d816c_0', '3d6d2a1e078a524c321be0ef1429c905cfe575f2cc0de213917f24f2e9665ae1_0', 'fd1375d616fa7faf9e7cb4afc4f6cc833d68f6411b892ea8898b79f2e4d8a2ad_0', '99f2b5db6e2811b000500528ad14656abbc83b246d4927c42654bc7264d90050_0', '9b4ba868faa82168de6953dbbd8f91fee7e69e8a274977d0fe19bdd8dd4e0b86_0'],
'C': ['07bb66c3a91c6bfe26d3e3c996699bc37a57e1bde5e43d13f98d6c1a5ccb4f0c_0', '05a193ff8152c1200f29f1e302c0268761cad740d0d02eb074bfd39835c766e9_0', 'cc359ee1ac4edd2e3a7955d8a430cc53ef7042a491ad97cd459297b3de8c144a_0', '15d79f6aa1e75340d4b4b63f18099d07e56b3134d4f7a9ee46bc3ee7187733ef_0', 'd51ad7e9d5486770ef34984dc7e869801fd5016e0d7b84b623051865af6bf8c7_0', 'b6036ad7918d5c3efbc69caddbd3bec3c2be6dba017eda136d34f1aa7fb9e4bc_0', 'd4973ff8c22e1f7d773eb659706d4308985f1c2ea1a9962fd8f507e56ba8cad9_0'],
'O': ['ae5d7719fac418ced00222d1697b70cda4dc6bc6a44abb2c762e273de1426fe0_0', '90a4de9e30b0a1923728ea343d18980876baa60d775a677e35fa50348e2c227a_0', '4a3a5d84498fc6c1f874aa6481ce3af3a9d084f9c776826035e5fdcefc464c1b_0', 'b912970e2d2fa97629ef586c1c0c3f0f4dbb5b19055fee101ed56d929a79cbb6_0', '38182021ca6af951f59186248433459a098b6415096bd137ee9143d34eea55b2_0', 'bf93f6f3271a72d162703ad8465fe9f9963525ff2b9990f558428e0dd860b662_0', '231c00e47da77ef2489047248f3671acf3fcc1a1c988c3d813ffbc1ddd08e359_0'],
'I2': ['fbf01bb5dad07909d8000c9271de4a48dec8240a5882cac949ac7f56f631e4f3_0', 'cedb6ad9376c8fc7987f5e0793ed7cb91a405b3a7dbd82149421ce0f4c3bf070_0', '1665d256de917d8f9118ffd06b809cc8d64b121bbc1a57ae77dea25b04fd869b_0', 'a9a025b792024e0e14c6f5ef3dcee9742355c38ea558565326576aedfa61b126_0', 'f57f4cbf8f16059d92ae7305820009caf7e90c5b3ac3b938933d1f7fadeeb733_0', 'c31a3aa9c6339bb9bf9bcebda94e2ac200ee4d756cece662db4de069bb85bae4_0', 'aa937719cada9eca0d0849baa1359fc738c8e789b12c94f4d8057be55f2419f1_0'],
'N': ['01d3da7957e822a56928b355c9ac03530aae2408c3fefd2d19c404ebcbb3a84b_0', 'd850d9e24f2c09e5099461ce4707151bf3b814f2385c1c45976d08c3b2dee1f3_0', '77aa728e854b48c3d3126767528472424f7813bde80c83109cac16818e94c4b1_0', '12300fa2608a5861887e2a9567770e08cb0bf8d5a5a3d29b53ed109a8d90f069_0', '9dbbbdf2228d87de77fcb896c64218d0a20fc2930d13ad6c898db6974a7891f3_0', '9499d23b6fbc83ef4191ebba09891b70745a40d93f5946e07a1fe6d5eec45a91_0', '2be763bef9a4017ceee386c8cd04f9f7ec812766c245ff5098cc7e7e7a397a99_0'],
};

In this script, each entry is a transaction ID of 1 image that will be used in the visual layer.

This example uses images that are letters used to spell the word Bitcoin. So, each layer corresponds to 1 letter in the word.

These layers are made to appear left to right, but you can composite an image from bottom to top as well. The bottom to top concept is what most PFP collections use.

Making Unique Pictures

Here’s where it gets interesting. To make our picture unique, we use a process called hashing. It’s like a super complex blender that takes some input and spits out a string of numbers and letters. We use the ordinal’s transaction ID as our input.

Our hashed output (that complex blend) determines which image we choose from each layer. This way, every unique transaction ID will create a completely random picture.

async function hash(input) {
const encoder = new TextEncoder();
const data = encoder.encode(input);
const hashBuffer = await window.crypto.subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}

Getting Our Layers Ready

Next, we use the blender (hashing function) on the last part of our transaction ID to get our first blend. Then we use this blend to select an image from the first layer. For the next layer, instead of using the transaction ID we use the first blend, which creates a new mix and use this new mix to pick the next layer. Each layer is picked using some randomness from the previous layer making it incredibly difficult for someone to try game the system.

async function generateHashesAndVariants() {
const pathname = window.location.pathname;
const lastSegment = pathname.substring(pathname.lastIndexOf('/') + 1);
const hashes = [];
const integers = [];
const layerKeys = Object.keys(layers);
for (let i = 0; i < layerKeys.length; i++) {
let toHash = i === 0 ? lastSegment : hashes[hashes.length - 1];
const hashValue = await hash(toHash);
hashes.push(hashValue);
const last12 = hashValue.slice(-12);
const integer = parseInt(last12, 16);
const modulo = integer % layers[layerKeys[i]].length;
integers.push(modulo);
}
return { hashes, integers };
}

The layers are chosen at random using a hash of the transaction ID, so each ordinal will be unique in that, what you see is a direct reflection of information Bitcoin was able to tell us about this ordinals unique characteristics.

Showtime

Once we have all our layers and images sorted, it’s time to put them all together and create our picture.

We have two steps here:

  1. Load the Images: We open up all the chosen images and make sure they are ready to use.
  2. Draw the Images: We place each image onto our canvas. We go in order, one layer at a time, until all layers are added.
function loadImages(images, callback) {
let loaded = 0;
let imageElements = [];

for (let i = 0; i < images.length; i++) {
let img = new Image();
img.onload = function() {
loaded++;
img.aspectRatio = this.width / this.height;
if (loaded === images.length) {
callback(imageElements);
}
};
img.src = images[i];
imageElements.push(img);
}
}

function redrawImages(imageElements, canvas, context) {
context.clearRect(0, 0, canvas.width, canvas.height);
for (let img of imageElements) {
let width, height;
if (canvas.width / img.aspectRatio < canvas.height) {
width = canvas.width;
height = canvas.width / img.aspectRatio;
} else {
height = canvas.height;
width = canvas.height * img.aspectRatio;
}
let x = (canvas.width - width) / 2;
let y = (canvas.height - height) / 2;
context.drawImage(img, x, y, width, height);
}
}

Making It Run

Finally, we take all these separate steps and make them work together.

window.onload = function() {
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');

generateHashesAndVariants().then(({ hashes, integers }) => {
let imageUrls = [];
let layerKeys = Object.keys(layers);
for (let i = 0; i < layerKeys.length; i++) {
imageUrls.push(`/${layerKeys[i]}/${hashes[i]}/${integers[i]}.png`);
}

loadImages(imageUrls, (imageElements) => {
redrawImages(imageElements, canvas, context);
});
});
};

Creating Your Own NFT Ordinals

What we have built here is a generator for unique pieces of digital art. Now, you might be wondering — how can I turn these images into NFTs?

That’s where the concept of NFT ordinals comes into play. An ordinal NFT is a special type of NFT that retains the order of its creation. It’s like a limited-edition print series where each print has a unique number.

To mint your generated images as NFT ordinals, you’ll need to interact with the Bitcoin network. The specific instructions for this process can vary, as it depends on the Bitcoin wallet or service you are using. It generally involves associating your digital artwork with a unique Bitcoin transaction, effectively turning that transaction into your NFT.

Here are the high-level steps for creating an NFT ordinal:

  1. Finalize your generated artwork and export it as a PNG or JPEG file.
  2. Upload this image to a platform that supports Bitcoin ordinals.
  3. Follow the platform’s steps to mint the base layers.
    (1satordinals.com is very easy and cheap)
  4. Copy/Paste the transaction ID’s of your layer ordinals into where we describe the layers in code.
  5. Publish your HTML ordinal.

Now that you’ve got your hands dirty with some coding and the world of generative art NFTs, it’s time to put your creativity to work! Go ahead, start creating new layers and variations to create your own unique art. And remember, every piece of art you create is an opportunity to mint a new NFT. Your creations are only bound by the limits of your imagination!

If you're interested, you can see the code inside the transaction:
https://whatsonchain.com/tx/62c39ccc237b247420f0262ca9a9a1e8f3f8a8775a86685e390fe085cf77b70d

Or in a GIST:
https://gist.github.com/ZacharyWeiner/b2e97e323682285f74efcd11d38c8285

If you liked this post or have any questions you can follow me on Twitter:
@developingZack

--

--

Zachary Weiner

Founder @MagicDapp.io & @AlphaDapp.com | Find @DevelopingZack on Twitter & Telegram