What I learned from building Cool Cats NFT

Solidity — seriously

Published in
10 min readJul 6, 2021


Cool Cats is the second solidity contract I have ever published on the Ethereum blockchain, and I was a solidity noob through and through.

We all have to start somewhere and I want to share my whole uncensored NFT journey with you.

How it all started

One day I spotted a cute pixel NFT and I instantly fell in love with the cute animated characters. I was early to the game but didn’t have much ETH in my wallet, so I bought just one.

I didn’t pay much attention to my new NFT until it hatched during the reveal event. I then spent the whole day looking at all the new characters that had been generated, digging around to figure out how everything was working and sketching out what I thought was the minting process, but everything seemed to link back to the solidity contract and it looked scary.

A few days passed by and I woke up one morning feeling code-brave. Cracked open Google and started trying to understand a few solidity contracts.

I failed.

In the end, I asked Lynq to help me dig into things and together we finally started to make some headway towards understanding NFTs. And it wasn’t until I worked my way through this article that everything really clicked.

Testing testing testing — MoonKids

With a basic understanding of how NFTs worked, I started to build out my own project on the Rinkeby testnet — MoonKids.

I knew I wanted to create a generative pixel project, but I didn’t have the time to really pixel a lot of graphics, so I headed over to this awesome dollmaker and created a ton of test images.

With the addition of a few layers: pets, planets, balloons, and backgrounds, I had all the graphical assets I needed. Time for the scary bit — solidity.

Honestly, I have lost count of the number of MoonKids test contracts I wrote. It took me a long time to finally nail down the logic behind Opensea showing the image from the metadata on their website.

I told you I was a solidity noob :)

Finally, I got everything working, and I even managed to figure out how to mask metadata pre-minting, so people can’t data hunt for the rarest tokens.

Time to hit the mainnet

I was feeling confident that I could now build a solid solidity contract, so I convinced Lynq (took all of 5 seconds) to help me out, hired some pixel artists, and began work on Faticorns.

The early days were mostly trying to figure out our character design and building our character layering system with PHP. This was the easy part as both Lynq and I have a combined 30+ years PHP experience. Yes, we are older than you might think :)

The artwork was an evolving process, but we finally settled on the above card format. The example is missing the randomized name and #id.

I put together a contract and we launched, selling to a single person — the awesome Shamdoo. Sadly, we had not perfected our system, and he felt the gas too high. We immediately stopped the sale and went back to the drawing board. And yes we fully refunded him, gas and all.

Queue the biggest headache of my life….

Optimizing solidity contracts

I actually love optimizing code (total geek right here). I have spent many countless hours battling code versions against each other trying to shave off milliseconds and CPU usage, but optimizing solidity felt like a completely different beast.

Trimming gwei for cheaper minting

To start off, I was writing small changes to my contracts and then deploying them to Rinkeby via remix. I would then run a transaction and compare the gas.

Epic noob right here :P

Obviously, this methodology was slow and just insane. I knew there had to be a better way — back to Google and StackOverflow.

I eventually found this article about hardhat and these articles about optimizing solidity contracts:

Finally, I had some solid info to work with, and we were off to the races.

I snuck in some contract optimization in between life and my IRL job, but progress was too slow for my liking, so I roped in another friend, Adam. The following weekend was really fun: Lynq, Adam, and I all pulled my latest git repo and tried whatever we could think of to lower gas.

Our goal was to get gas as low as possible, so minting was cheaper for the user and we found some interesting methods.

Local variables

A lot of NFT contracts seem to borrow code without fully understanding what it does or even bothering to correct mistakes or optimize it. A very common issue (if you can call it that) is the unnecessary use of the totalSupply() function.

function adopt(uint256 num) public payable {
require(totalSupply() < MAX_TOKENS, "Sale has already ended");
require(num > 0 && num <= 20, "You can adopt minimum 1, maximum 20");
require(totalSupply().add(num) <= MAX_TOKENS, "Exceeds MAX_TOKENS");
require(msg.value >= calculatePrice().mul(num), "Ether value sent is below the price");

for (uint i = 0; i < num; i++) {
uint mintIndex = totalSupply();
_safeMint(msg.sender, mintIndex);

Every time totalSupply() is called it costs the user, resulting in higher gas prices.

This could be written like so:

function adopt(uint256 num) public payable {
uint256 totalSupply = totalSupply();
require(totalSupply < MAX_TOKENS, "Sale has already ended");
require(num > 0 && num <= 20, "You can adopt minimum 1, maximum 20");
require(totalSupply.add(num) <= MAX_TOKENS, "Exceeds MAX_TOKENS");
require(msg.value >= calculatePrice().mul(num), "Ether value sent is below the price");

for (uint i = 0; i < num; i++) {
_safeMint(msg.sender, totalSupply + i);

This simple change reduced gas significantly, but wait there’s more!


Everyone knows how to use conditions, but when do we really think about what is actually happening?

Have a look at this line:

require(num > 0 && num <= 20, "You can adopt minimum 1, maximum 20");

Clear in its functionality, but in reality, it’s doing too much.

It is safe to assume that no user is going to try and mint 0 tokens, so immediately we can remove that, saving some gas — about 3 or 4 gwei (it all adds up)

require(num <= 20, "You can adopt maximum 20");

This less than or equal to is actually doing two things and both cost gas:

  • num < 20 ~4 gwei
  • num == 20 ~4 gwei

But if you simply change the 20 for 21 you no longer need to compare for equals and your line of code becomes.

require(num < 21, "You can adopt maximum 20");

I told you we got a little obsessed with reducing gas.

Ordering if blocks

When Faticorns first launched, it had a bonding curve, and we tried to optimize that before ultimately replacing it for flat pricing. However, in trying to optimize it I learned a valuable lesson — order your blocks.

function calculatePriceForToken(uint _id) public view returns (uint256) {
require(_id < MAX_TOKEN, "Sale has already ended");

if (_id >= 9900) {
return 1000000000000000000;
} else if (_id >= 9500) {
return 640000000000000000;
} else if (_id >= 7500) {
return 320000000000000000;
} else if (_id >= 3500) {
return 160000000000000000;
} else if (_id >= 1500) {
return 80000000000000000;
} else if (_id >= 500) {
return 40000000000000000;
} else {
return 20000000000000000;

In the above code, all early minters (low ids) have to compare all the if/else until they hit their level. This costs gas. If you reverse this block or at least organize your if/else block so that higher populations find their position sooner, then the overall gas is less. I believe Adam found this golden nugget.

Solidity maths — fear it

This is a massive topic, I’m just going to cover a few very simple bits I learned.

There is no number <0 in solidity and no floating points either. If your maths creates either, expect crashes and transaction failures.

Knowing that most people use a library called safeMaths.sol.

My only issue with safeMaths is its overuse. You don’t have to use safeMaths to do (please correct me if I’m wrong):

uint256 value = 100 / 2;

Using safeMaths for this just adds extra gas costs.

However, please do your own research as I am clearly no expert.

Building Cool Cats

While building Faticorns, Lynq and I teamed up with Clon and Evan and started work on Cool Cats. We figured it couldn't hurt to get more experience.

Cool Cats solidity contract

The contract really has no fancy bells or whistles, it simply allows users to mint an NFT token and gives me the ability to start and pause the sales, change price and change the baseUri — that’s basically it.

You can view the contract here

Metadata server

You have probably seen numerous NFTs with blind minting. People mint their tokens and at some point in the future, they are revealed.

Blind minting can be a cool feature, but Cool Cats show their rarity via their backgrounds:

Random screenshot of cats from opensea

We wanted users to mint and be able to view their cats instantly, recreating the feeling of opening Pokemon cards as kids. This meant we needed a way to mask metadata pre-mint and instantly show the real metadata post-mint.

I played with a few different methods and eventually settled on a surprisingly simple solution (sharing the test version, not the final).

async writeIfMissing(id){
//check if file exists
try {
const file = `./sold_tokens/${id}`;
const final_file = `./final_metadata/${id}`;
// file missing
if (!fs.existsSync(file) && fs.existsSync(final_file)) {
try {
await this.logTransfer(id);
} catch (err) {
} catch(err) {
async logTransfer(id) {
try {
var oldPath = `./final_metadata/${id}`;
var newPath = `./sold_tokens/${id}`;

fs.copyFile(oldPath, newPath, function (err) {
if (err) throw err
} catch (err) {

I simply listen for transactions using web3 and check if the file exists in a sold_tokens directory. If it’s missing, just copy the relevant file into place.

This code also runs on a 5 min timer to catch any that web3 might miss.

Simple and no complicated moving parts to go wrong.

Character builder

This simply uses good old PHP to layer the asset images on top of each other to create the desired end product:

<?php$x = $y = 1080;
$final_img = imagecreatetruecolor($x, $y);
imagealphablending($final_img, true);
imagesavealpha($final_img, true);

// background
$hex = hex2rgb($GLOBALS['config']->tier_bg_colors->$tierName);
$bg_color = imagecolorallocate($final_img, $hex[0], $hex[1], $hex[2]);
imagefill($final_img, 0, 0, $bg_color);

$body = imagecreatefrompng("./animated_images/Body/basic blue/1.png");
imagecopy($final_img, $body, 0, 0, 0, 0, $x, $y);

$shirt = imagecreatefrompng("./animated_images/Shirt/$combo->shirt/1.png");
imagecopy($final_img, $shirt, 0, 0, 0, 0, $x, $y);

What's next?

We have big plans for Cool Cats, one of which is breeding on a future contract and generation 2. How this will work is still being discussed with the community.

For now, we are focusing on building the Cool Cats universe. A town or city where they all live together. A simple way to root the community and bind them together to create a feeling of belonging and ownership.

Part of that ownership might even include a way to rename your cats. More on that in a separate post.

All that being said, we take our orders from the community. If the community decides against anything, we scrap it.

Where to find me

I can normally be found in the Cool Cats discord channel

Or on Twitter


Last Word

I feel silly saying this, but I have had so many messages I feel it might save you and me some time.

I have a full-time job IRL and coupled with Cool Cats I am swamped with work, so sadly I have to decline every job offer.

But I am always willing to help out by sharing resources and advice where I can.

Join Coinmonks Telegram Channel and Youtube Channel get daily Crypto News

Also, Read




Im a Found of Cool Cats, general Web 3 consultant with 20 yrs experience as a builder, marketer and company owner in Web 2.