Onchain Block Invaders (OBI)

Overview

Onchain Block Invaders (OBI) is a fully on-chain NFT collection on ethereum network. The most remarkable thing about this collection is that all of the metadata belonging to the NFTs are stored and generated on-chain as well as having modular contracts that gives holders the chance to have interchangeable skins and color palettes for their NFTs.

Deep Dive

One of the most interesting things about this collection is that thanks to its fully on-chain metadata and modular contracts holders can switch between skin and color contracts enabling them to have a completely different looking NFT while preserving their right to switch. While this switch process requires a small gas fee as it is an on-chain transaction, platforms that already have cached images can display all the current and alternative looks of an OBI by querying the assets (skins and color palettes) a holder has.

Modular contracts lets us to render our OBIs with a totally different configuration in the future which is pretty exciting as it opens a door for endless possibilities. For now there are 3 main contracts which are as follows:

1- Mint Contract:

What color palettes and skins an OBI have can be registered here. Every OBI can be combined with 32 color palettes and 32 skins which makes up to 1024 combinations. Minting new assets for OBIs in the future will not lead to minting of new NFTs but rather unlocks new looks for existing OBIs

2- Skin Contract

In charge of generating and storing skins on-chain

3- Color Contract

This contract will store and generate new color palettes and SVG effects that can be applied to each skin that one holds

Gas Optimization

Contracts reduces gas by generating images through strings as they can be tightly packed while uploaded to the chain. The team claims that they have used a utils library for strings which have made their job much more easier. If you are dealing with complex string operations I highly advice you to check out the Github repo of the library:

The library for string operations
The library for string operations

You can further read about the gas reducement on generating images on-chain from the following Twitter thread:

On-chain storage cost

The entire collection is about 17kb. The fee of storing a 256 bit word is 20k gas. In the time of writing this article average gas price is 10 gwei. This means that the cost of 1kb is as follows:

20000 * 10 * 0,00000001 * 1000 = 2 USD

as 1 gwei equals 0,00000001 ethers and 1 ether equals 1000 USD. Thus we can say that the total cost is around 34$

Gifting System

As the team highly values building relations inside the community they have created so-called gifting system in which holders will be able to mint skins or colors to other holders that can be tied to seasonal events.

Roadmap

  • Contract showcase - Date: 2 days before mint day
  • OBI presale day - Date: 11.06.2022
  • OBI public mint day - Date: 12.06.2022
  • Minting of the second color palette (Dark Side) - Date: TBA
  • Minting of the second skin (New Faction) - Date : TBA
  • Community voting on the utilities of skins and colors - Date: TBA
  • OBI Game - Date: TBA

The OBI Game

There will be a nostalgic retro game in which you can earn giveaways, WL spots, skins and palettes by interacting with your assets (skins and color palettes).

Technical Review

ERC721OBI

Most of the IERC721 function implementations are similar to ones of Openzeppelin except some util functions which I will mention soon and the ownership state of tokens are held in a dynamic array with the identifier owners in which index of the array represents the token id whereas the value is a struct called _contractStruct in which there are 7 fields as follows:

Honestly at first I had a difficult time understanding what all those field for which I guess is because of the bad naming. idx1 represents the skin index this OBI uses and idx2 is the color palette index used by the OBI. cnt1 and cnt2 are the number of skins and colors the OBI have currently in order. bitmap1 and bitmap2 are the data structures from which the mint contract is able to check if the OBI has the newer skins and colors (to see if the OBI can mint the skin or color )as well as storing the skins and colors minted historically. And the account field is obviously for storing the holder address.

The utils functions are for checking if the OBIs have the newer skins and color palettes and if not minting those new assets for the OBI.

function isBitSet( uint32 _packedBits,uint8 _bitPos) internal pure  returns (bool){
        uint32 flag = (_packedBits >> _bitPos) & uint32(1);
        return (flag == 1 ? true : false);
    }
    
    function setBit( uint32 _packedBits,uint8 _bitPos)  internal pure  returns (uint32){
        return _packedBits | uint32(1) << _bitPos;
    }

    function  countSetBits(uint32 _num)  internal pure  returns (uint32)
    {
     uint32 count = 0;
     while (_num > 0) {
            count = count + (_num & 1); // num&1 => it gives either 0 or 1
            _num = _num >> 1;	// bitwise rightshift 
        }
    return count;
}

Here one of the most confusing things is to understand how the skin and color infos (whether they were minter or not) are stored. The team’s decision is to use bits for each color or skin for optimization. As I mentioned before the bitmap variables are uint32s which exactly hold 32 bits (do you remember we mentioned you could have up to 32 skins and colors). Each bit represents a different skin or color while the functions above are operations that manipulate those bits. To elaborate, isBitSet function checks if the bit with index _bitPos is non-zero (the holder haven’t minted the skin or color with that index) while the setBit function returns a new uint32 which switches a bit of _packedBits of given index.

BlockInvaders ( Mint Contract )

First of all I want to mention how the modular contract system I stated before works allowing totally different rendering chances in future skins

mapping(uint256 =>address) private _tokenIndexToAddress;

this mapping stores the rendering contract addresses from each skin index an OBI has (To be objective again the identifier here is not clear about what its purpose is). So whenever one decides to switch the skin his/her OBI uses (like the morphOBI function) he/she will trigger the following statement

_owners[tokenID].idx1 = skinNr;

which may cause the OBI to be rendered with a different contract as the tokenURI function returns the following:

IMotherShip  motherShip = IMotherShip (_tokenIndexToAddress[_owners[_tokenId].idx1]);
        return motherShip.launchPad(_tokenId,_owners[_tokenId].idx1,_owners[_tokenId].idx2,_owners[_tokenId].cnt1,_owners[_tokenId].cnt2); 

The launchPad function of MotherShip interface renders the OBI depending on the given parameters. However the key point to understand here is that by running the previous code we actually change the MotherShip contract we are interacting with.

Some minor details in code that could be better

Directly returning _tokenIndexToAddress[skinIdx] would be sufficient
Directly returning _tokenIndexToAddress[skinIdx] would be sufficient
== true is redundant
== true is redundant
Checking isBitSet twice with the same parameters is redundant and gas consuming
Checking isBitSet twice with the same parameters is redundant and gas consuming

Conclusion

Overall, I believe the team made a pretty innovative job in a such period where most of the NFT projects are the imitation of each other. Both the concept and the way the code is written is unique and opens some exciting doors for the NFT space. Hope new projects that uses similar techniques will emerge as a life without surprises would be like crypto without Ethereum :)

Subscribe to What The HCK
Receive the latest updates directly to your inbox.
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.