Explaining its functionality by grouping lines of code
The CryptoKitties project has always been interesting to me. It was the first popular NFT game ever. But I didn’t understand its appeal, why it blew up. I also never quite understood the game dynamics. So I decided to finally learn what the game is about and how it’s implemented under the hood.
It was clear to me that the NFT part was implemented as an ERC-721. But I wanted to understand how breeding is implemented. What’s kept on-chain vs off-chain? As I started exploring the game, I learned that it has an auction mechanism. How is that implemented?
And how much do people actually earn from this thing? Are people still playing it? Or has it been entirely superseded by more successful Axie Infinity?
In this article, we will answer all these questions and break down the smart contracts behind CryptoKitties. Here is the outline of this article:
- What is CryptoKitties?
- Code structure (core, breeding, auction)
- Timeline of CryptoKitties
- My opinion on the code
At first, I wanted to do a breakdown of the Axie Infinity(AI). But turns out AI does not open source most of their Smart Contracts (SCs). So I pivoted to CryptoKitties(CK) (its contracts are public). It’s a simplified version of AI with similar game dynamics:
- Blockchain-based, play-to-earn game
- People collect and trade kitties
- Can breed two kitties to get a new kitty
- Earn real ETH by selling your kitties or renting them out for breeding
- Breeding works based on “genes”. Child kitty gets a mix of parents’ genes
- Kitties do not have a gender
- CK makes money by charging a cut in its marketplace (auctioneer fee) and minting new kitties
- To enter the game you need to purchase some kitties
Here is a video of the CK gameplay if you’re interested.
Axie Infinity was inspired by CryptoKitties, Pokemon (for battling), and later Clash of Clans (for the lands). More on the origins and the business side of AI.
CK has 3 smart contracts: Core, Breeding, and Auction.
The core contract is broken down into many sub-contracts:
KittyBase contract inherits/extends
KittyCore has everything combined.
KittyAccessControl: creates 3 roles: CEO, CFO, COO, and restricts access from some functions to these roles. CEO can reassign roles, change points to sibling contracts. CFO can withdraw. COO can mint new kitties.
KittyBase: data structure of kitties; stores all kitties and ownership info; transferring of ownership.
KittyOwnership: implementation of ERC-721 interface. I explained the implementation of ERC-721 in my BAYC smart contract breakdown. Check it out if you’re interested.
Did you know that CryptoKitties actually pioneered ERC-721 standard?
KittyBreeding: will be explained in the “Breeding” section.
KittyAuction: will be explained in the “Auction” section.
KittyMinting: only 50K kitties can be minted — 5K are promo kitties, the rest are regular gen0 kitties. (Difference: promo can be transferred to a specific address at the mint time, regular gen0 can only be auctioned). Any genes can be specified during minting.
KittyCore: ties everything together, adds payments/withdrawals, and handles upgradeability – covered later in the article.
Payments in Ethereum 101:
– to accept a payment, just make your function
msg.valuevariable has the amount that was sent
– to send a payment to an address, just use
The breeding logic is implemented in the
KittyBreeding sub-contract of the core contract.
- First, there are a bunch of helper functions like
- Then there are 2 functions that actually do the breeding.
breedWithstarts the breeding process and
giveBirthcall will succeed only after the gestation period is complete.
Midwives and auto-birth
We can assume that
breedWith is called from the front-end of CK when someone initiates breeding. But how is
giveBirth called? There are no callbacks or
cron jobs in Solidity. So someone needs to call
giveBirth in the future.
This is where midwives come in. CK has a network of autoBirth daemons which call
giveBirth at the right time. Anyone can set up a daemon.
giveBirth costs gas. Why would daemons pay gas for someone else? This is where
autoBirthFee comes in. When a player initiates breeding, he needs to pay
autoBirthFee to CK (0.04 ETH currently). CK will later compensate the daemon when he calls
Super-secret genetic combination algorithm
You probably noticed that the
giveBirth function calls
mixGenes function to get the child genes. This
mixGenes function is actually a part of a sibling contract called
GeneScience (source code: v1 and v2).
KittyBreeding just stores a pointer to
GeneScience was not open-source to “deliberately cultivate mystery and discovery around the CryptoKitties genome”. But the community built tools to reverse engineer the gene science algorithm. CK then open-sourced it and even released a second version with slight improvements(CK_blog_post).
GeneSciencecontract: v1 released in Nov 2017, open-sourced in Jan 2019, v2 released in Feb 2019 (already open-source).
I won’t cover
GeneScience code because it’s too low level. But I will highlight a few points.
GeneScience involves lots of bit manipulation to mix the genes of the parent kitties. Remember that genes are stored as a 256-bit number in the
Kitty struct. These bits are mapped to traits (or cattributes as CK likes to say) that determines the appearance of the kittie.
How is randomness generated?
Solidity does not have a random number generator so CK uses the block number of when the offspring is born as a seed for randomness. This block number is hard to manipulate so it should give enough randomness
The final contract of CK is for auctions. CK uses a “clock auction“: you set starting and ending prices and duration. The price then changes linearly from the starting to the ending price. Whoever bids first, wins.
Here is the structure of the auction contract and how it fits with the rest:
- ClockAuctionBase: keeps track of existing auctions and a function to bid
- ClockAuction: Just a wrapper on top of
SaleClockAuction: auctions for renting out your kittie for breeding and for selling your kittie, respectively. These need to be separate because the actions taken after a successful bid are quite different for each case.
SaleClockAuctiondoes not add much except tracking the last 5 prices of auctions used to set the optimal auction starting price for the newly minted kitties.
SiringClockAuctionis yet another wrapper on
ClockAuctioncontract, except it transfers the rented kittie back to the owner instead of the bidder (bidder keeps the offspring).
They are linked to the core contract in the
To summarize auctions: you create an auction for your kittie, it’s transferred to the auction contract and a new auction is created. Whenever someone bids successfully, either the kitty is transferred to the bidder (when it’s a selling auction), or the kitty is transferred back to you but the bidder keeps the offspring (when it’s a siring auction).
We saw the code. But the code is static, it doesn’t tell us the dynamic picture. What happened after deploying the contract?
You can inspect the entire history of transactions of this contract on Etherscan. If we go way back to the beginning:
We can see that the contract was on Nov 23, 2017. Then set the addresses of the sibling contracts and set the CEO/CFO roles. Then they created a bunch of promo kitties (3K to be exact).
Then they unpause the contract (which means most functionality is now available) and the action began:
Here is the contract balance over time (from Etherscan analytics):
The sharp declines are the withdrawals by the CK team. We see that most of the action happened from Nov 2017 until Nov 2018. From the CK timeline website, it looks like CK had 250K users at the peak in Jan 2018. Also in Dec 2017, CK accounted for 25% of traffic on Ethereum.
Here is the graph of transaction frequency:
While there are still a significant number of transactions from Nov 2018 until Jul 2019, they are all low-value because the balance stays relatively flat in that region.
- first and foremost, the art does not live on-chain. The genes are kept on-chain but ideally, the images of kitties would also be generated on-chain (like Art Blocks).
- CK did not even put the links to the artwork on-chain. They could have used the
getMetadatafunction to return the links to immutable images. Instead, they just return “Hello World” 🤷♂️. The mapping from
tokenIdsto images happens on the frontend. So if CK were to shut down their website, you would only be left with a meaningless 256-bit number.
- Upgradeability is not ideal. The core contract can be updated via setting a new address in the KittyCore contract. In that case, an event will be emitted and it’s up to the clients to listen to it and switch to the new contract. The old one will be forever paused.
Now onto the positive
- Good separation of concerns: Breeding and auction are separate from the core contract to minimize bugs. You can plug in updated versions of the sibling contracts without disrupting the core.
- Clean code and a solid understanding of Solidity. Lots of useful comments throughout the contract. Guarding against reentrancy attack and maximizing gas efficiency: