OnChain Dinos — A Deep Dive

Apex777.eth
8 min readJan 10, 2024

--

You are probably wondering where all these cute little Dinos all over your base timeline have come from, in this article I’ll go over our launch and a deeper dive into how the Dinos are stored onchain.

Hi, I’m Apex the developer behind the Based OnChain Dinos 👋

Collection Overview

Based OnChain Dinos is a collection of 2,000 NFTs, each randomly generated and distinctly determined at the time of minting. These Dinos are stored entirely onchain, including images and metadata.

Name: Based OnChain Dinos
Chain: Base
Supply: 2,000
Price: 0.0025 ETH

Links:
Opensea: https://opensea.io/collection/based-onchain-dinos
Contract: 0xD4c5292b9689238f0A51C8505B1d1D6714Ce95a0
Discord: https://discord.gg/J47bWRPuYs
Twitter: https://twitter.com/OnChainDinos

Thank you to Tiny Dinos for the amazing cc0 art!

How Did the Mint Go?

Based Onchain Dinos launched on the 8th of January on the Base network and sold out in less than 6 hours.

2,000 Dinos found new homes, with 81 of these going to free mint whitelist holders. At the time of writing there is 449 unique holders.

We got featured on in ONCHAIN DAILY on the official Base Twitter account

Secondary Sales

Once sold out, volume quickly moved to the secondary market via Opensea over the last 7 days Based OnChain Dinos has see over 21 ETH of volume trending on the Base chain daily and trending on the front page of Opensea under “all Chains”

Opensea:
https://opensea.io/collection/based-onchain-dinos

OnChain Mechanics

Now onto the fun stuff, as mentioned in our previous article all 2,000 NFT’s have been stored on the blockchain completely, they do not rely on any external HTTP / IPFS dependencies to get your Dinos image or metadata values.

I went through this briefly in a Spaces on how the Onchain images and metadata works, have a listen here but it’s hard to show how code works over a voice call. Hopefully I can provide some more details here.

OnChain stores NFT metadata directly on the blockchain, ensuring security and immutability. OffChain relies on external platforms, offering flexibility but sacrificing decentralization.

Off Chain metadata usually relies on a HTTP or IPFS hosting the actual json files and images for a collection:

OffChain Tiny Dinos Example

In Onchain metadata we build the metadata for each Dino using solidity code, keeping all of the NFT’s details completely on the blockchain.

This also applies to each Dinos image. We store every trait a Dino can have in byte arrays that represent an svg image of that trait and build the dino in code. We’ll dive deeper into this in the next part.

Image Onchain example

PNG to SVG to OnChain

Before we touch any solidity code, we needed to get every Dino trait converted to SVG. We need SVG, as this is an standard that uses code to create and show an image. We took 71 different Dino pngs and converted them to SVG.

Luckily, the Tiny Dinos have made all trait assets of Dinos available for anyone to download and mess with, this is the beauty of cc0 art. Here is what that looks like, we have 8 folders each filled with different elements of a Dino.

Body
Head

Using some nodejs scripts along with some manual tweaks we get an svg file made entirely of “rect” elements that recreate the trait of a dino. Here is an example of the yellow gradient body.

SVG of a Dino Body

Let’s look at one rect element a little closer:


<rect x=”6" y=”5" width=”1" height=”1" fill=”rgb(215,210,170)” />

What we have here is an “x” and “y” coordinate, this tells use where the square goes in the 16x16 view box along with “height” and “width”, this tells use how big that square should be. Finally we say what color this square should be using “rgb”.

so now we have a pattern we can follow. We can condense this down to the following for the entire yellow gradient body asset.

6,5,215,210,170,7,5,215,210,170,8,5,215,210,170,9,5,215,210,170,6,6,218,209,145,7,6,218,209,145,8,6,218,209,145,9,6,218,209,145,10,6,218,209,145,6,7,218,207,126,7,7,218,207,126,8,7,218,207,126,9,7,218,207,126,10,7,217,207,126,6,8,222,209,111,7,8,222,209,111,8,8,221,209,112,4,9,223,209,90,6,9,224,208,90,7,9,224,208,90,8,9,224,209,90,9,9,223,209,90,5,10,222,204,71,6,10,222,204,71,7,10,222,204,71,8,10,221,204,72,6,11,225,205,44,8,11,225,205,44

Unfortunately, storing strings on solidity of this size is not an option even with the cheap fees of Base. So we need to convert this String to hex bytes that we can store easier.

Here is the same string, stored as a hex:

0x0605d7d2aa0705d7d2aa0805d7d2aa0905d7d2aa0606dad1910706dad1910806dad1910906dad1910a06dad1910607dacf7e0707dacf7e0807dacf7e0907dacf7e0a07d9cf7e0608ded16f0708ded16f0808ddd1700409dfd15a0609e0d05a0709e0d05a0809e0d15a0909dfd15a050adecc47060adecc47070adecc47080addcc48060be1cd2c080be1cd2c

We repeat this for every trait, in every category and we store these elements as a byte arrays in our Dino smart contract. Here is the feet structure below, we’ll use this as it’s just 4 elements. Normal, Hoverboard, Rocket boots and skateboard.

//// SVG data of attribute
bytes[] internal feet_data = [
bytes(hex''),
bytes(hex'0009dddddd000adddddd010adddddd000bdddddd020bdddddd000cdddddd010cdddddd020cdddddd030cdddddd010ddddddd030ddddddd050dc566bf060dc566bf070dc566bf080dc566c0'),
bytes(hex'0108dededd0109dededd0209dedede020adededd030adedede010bdededd030bdededd040bdedede020cdededd040cdededd050cdedede060c181817070cdedede080c181817'),
bytes(hex'040b7b4c390a0b7b4c39050c7a4b38060c7a4a38070c7a4a38080c7a4b38090c7a4b38060db9b0ac080db9b0ac')
];

/// Name of Attribute
string[] internal feet_traits = [
'normal',
'hoverboard',
'rocket boots',
'skateboard'
];

/// Probability array
uint[] internal feet_probability = [76, 84, 92, 100];

If we pass in an array element number to feet_data and feet_traits we would get back the hexcode for the svg and the name of the trait.

Use can use our public method here to pass a token ID and get back your svg code for a dino.

svg build method in smart contract
SVG Call

pasting this code into https://www.svgviewer.dev/ will show your dino in all their glory, try it out here

Dino SVG

Saving Traits against a TokenID
Ok, but how do we know what tokenID gets what Dino traits? Well lets dig into that here in this section.

Each Dinos traits is stored in the following structure in our smart contract:

/// Dino Trait Struct
struct TraitStruct {
uint body;
uint chest;
uint eye;
uint face;
uint feet;
uint head;
uint spike;
}

the number against each element represents what place in the trait data array the trait is stored, so for example my Dino, TokenID number 1 has the following properties.

TokenID 1

My Dino, has 11 against body, this means they have a rainbow body, as this is the 11th element in the array. This applies for the rest of the objects in this structure.

Body Arrays

Randomly Assigning traits during minting
So, we now have all the assets onChain, we have the ability to store these assets against each NFT tokenID. The next step is to determine who gets what traits and how rare each trait is.

You may have noticed this array in a previous code snippet:

/// Probability feet array
uint[] internal feet_probability = [76, 84, 92, 100];

This is what we call a cumulative weighting system. It allows use to make certain traits rarer than others, or less or more likely to come out when a Dino is minted. Let’s look at the feet example above in more details.

We have 4 traits:

  • Normal
  • Hoverboard
  • Rocket Boots
  • Skateboard

We don’t want 25% of every token to have rocket boots, we’d prefer to have this be a rarer and more unique trait. To do this we assign a value for each trait in “feet_probability” with the values above, this is the probability of a token getting that trait:

  • “normal” has a 76% chance (1–76).
  • “hoverboard” has an 8% chance (77–83).
  • “rocket boots” has an 8% chance (84–91).
  • “skateboard” has a 8% chance (92–100).

Our solidity code will get a random number between 1 and 100 and the trait whose cumulative probability range includes this number is selected.

Pick trait by Probability

For example, if the random number is 81, the selected feet trait would be "hoverboard" because 81 falls in the range 76-83. This system allows for the random but controlled selection of traits.

This system is applied to all trait categories is what gave us some pretty cool and rare Dinos. If you jump into our Discord you’ll see a full list of what probabilities each trait had.

Pseudo-randomness OnChain

To get all 8 different Dino trait categories randomly, we need 8 uniquely different random numbers to pass into each trait array. This number must be between 1 and 100 to help pick a trait using the weighted trait system in our smart contract.

// Initial seed
_randomSeeds[0] = uint256(keccak256(abi.encodePacked(_lastBlockHash, _tokenId))) % 101;

// Generate subsequent seeds
for (uint i = 1; i < 7; i++) {
_randomSeeds[i] = uint256(keccak256(abi.encodePacked(_randomSeeds[i - 1], _tokenId))) % 101;
}

This is done in the code above using the keccak256 solidity function, it basically turns whatever variables you pass in, into a number. A very large number.

The contract generates one random seed using the hash of the last minters blockhash and the token’s ID.

We then use this seed, to generate 7 more seeds.

All 8 seeds are then applied against “modulo 101” to get use a number between 1 and 100.

OnChain Summary

We have kind of gone backwards here in explaining how the process works, but lets summarize here quickly:

  • During minting, we get 8 random numbers between 1–100
  • Each of these random numbers is used to pick a trait from our 8 different trait categories.
  • This trait info is saved in a mapping against the tokenID for this specific Dino.
  • Using the trait mapping we can build each Dinos metadata, including images. This method keeps all dependencies internal on the Base blockchain and immutable.

Summary

If you enjoyed this deep dive into Solidity and onchain NFT’s please share the article, if you have any questions drop into our Discord or DM me on Twitter.

--

--