3 different ways to make on-chain generated NFTs

Why?

NFTs are a trend right now and most part of them is seeking a way to store data without the need of centralized servers. Imagine you pay 200k on a BAYC and in one day, their servers goes off and your ape simply lose all it’s value, that’s kinda awful, right?.

Background

Leaded by a small group of devs that created great projects such as Anonymice, The ChainRunners, Ether Orcs and more, the on-chain generation revolution has is slowly becoming the standard, so I want to share with you some of my ideas and examples to implement it on your next project.

Examples and Ideas

1. Blockchain stored images

Pros:

  • Can be extended easily by uploading more assets.
  • Assets can not be removed or lost.
  • Less need for coding.

Cons:

  • Costs a lot of gas to deploy.

In order to implement this, you would need to do some functions to input the data and store it on a mapping or array. Wolf Game used it and I guess it worked very well 😀 but here’s another way to do it.

// Define the input format.
struct Trait {
	string value;
        string png;
}

/* Create an array where they will be stored and
implement the function that will store the inputs */
Trait[] public backgrounds;

function _addBackground(PixelCityLibrary.Trait calldata _background) internal {
	backgrounds.push(_background);
}

2. Algorithmic generated images

Pros:

  • Can generate pretty unique outputs.
  • Won’t cost you extra gas.

Cons:

  • Requires great coding skills.
  • May require external tools for encoding, compression or generate different structures.

This option is by far the most hard and I, personally, don’t think it’s a good idea. You must create a unique algorithm that decodes bytes into a an image, pixel by pixel.

I’ve never tried it but you can read about it very well explained on the Nouns DAO docs or via the Chain Runners contract.

3. “Hardhcoded“ assets

The last one is very simple and we can see it’s implementation on the Ether Orcs contract. All assets are pre written in separate contracts (to avoid exceeding size limit) and retrieved on every call, then put together as layers of an SVG, pretty like the first option I presented to you.

// This code was extracted from the original Ether Orcs' Invetory Manager smart contract.
mapping(uint8 => address) public bodies;
mapping(uint8 => address) public helms;
mapping(uint8 => address) public mainhands;
mapping(uint8 => address) public offhands;
mapping(uint8 => address) public uniques;

function call(address source, bytes memory sig) internal view returns (string memory svg) {
        (bool succ, bytes memory ret)  = source.staticcall(sig);
        require(succ, "failed to get data");
        svg = abi.decode(ret, (string));
}

function get(Part part, uint8 id) internal view returns (string memory data_) {
        address source = 
            part == Part.body     ? bodies[id]    :
            part == Part.helm     ? helms[id]     :
            part == Part.mainhand ? mainhands[id] :
            part == Part.offhand  ? offhands[id]  : uniques[id];

        data_ = wrapTag(call(source, getData(part, id)));
}
    
function wrapTag(string memory uri) internal pure returns (string memory) {
        return string(abi.encodePacked('<image x="1" y="1" width="60" height="60" image-rendering="pixelated" preserveAspectRatio="xMidYMid" xlink:href="data:image/png;base64,', uri, '"/>'));
}

The uniques contract looks like this:

contract Uniques {
  string public constant unique52 = 'iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAMAAAANIilAAAABSlBMVEUAAAD///8AAAABAQEAAAABAQEAAAABAQFUAAAAAAABAQEAAAAAAB6RkZEAAAD+2toAAAAAAAAAAAAAAAAAAACTk5MWEyQWFCQlIzIlJDPrlZUmJDMmJDIlIzIAAAAPDw8UFBQZGRkkJCQmJDMXFCUAAABLS0uamprSYmDn5Nv///+ZmZn29O3/zc0mJDN4eHjtl5dsLzLVoyT/GwFSITM2NjalQ0Gurq7/3NyicgCNIiCZBABjY2P/XQH/mjqOjo6NjY3g3tUaGhri39bm49r/wJ2BMTCLi4vi39cBAwKPj4/b2dGRkZH/4U/k4dnk4tkGBgY0NDQODg7/9cT8+fMYFSUFBQUgICDS0tJXV1eQkJDc2tLd2tPf3NTl4toBAAEUFBQVFRV6enqVlpaWlpaYmJh+fn7g3dVHR0cWEyQlIjD/7ZQlTUqsq6nl3NlIAAAAJHRSTlMAAAEBAgIDAwMEBAYICw0OECAmMUBw7O7z9fb9/f3+/v7+/v4EklekAAADWklEQVR4Xu3UV4/bVhCGYVFrp9lO707PzJxGUtrai2sv6b33/v9v883waJcmqYWMXOTGr4gFJOHRDAmcHRX/of8FP8QP8fiR8Wh85ulnnnv+2dHoAfG5Sz98+Ob3b19657O33l86M1oYM3NRfHDny8MLh9cv3Lp1/cdPnxwx80KYE+J/vvrt68s3vrl48fLtXxLrZ7wA5jStqpTCjXvvXfn2StBSqqpqCr0gDrkHxG98/uog3m01BzOuF6ZTxW09nU5349Wr+/v7q6tzMUfVKXXxT2l39eDgwOTdle0+NjuZqGbu4t1VpBINY1jD4ADJWDrCjfxrZeVEPDbMH0Phb8ZGDQ9Oho3Q/An6s7v2ykc60/hcDM2lCN0MDFNNVfIRtqUV0xDe3NyMHKC/M2Ox3TlwXlqxoyGMFAmbyjZPtmz4doA+EUNdu2Y6YzC7Y8VhLk4ibDbGRqd00+45e8VCwzglAWgFLMA2Vl/bwK6L/+A4USy2dxuLA86BugFMnCYTw6pTy0oAxt0aDUOYJGD0RHFZlpJmwZalYdDtEE07oftxCJwarLr8dVaJgCH39jhoDvXw7xwNoxjLMoQv8DsxKtB9Of7MTm0PE4yOZiIXcsDtHEcmuGGcmCdsO0QDTlx2GO+ccMTe2KiLbbAIJ8UNcU5EnPHmMZHuXWqdB5bXUQwNYEoUGscsyThGYNg+3uPmnRjOTsRsoVjm4KqqgIlUi2mX7ZoGzJGdOKwN28ZmSfbEeypobW251cx6fWIeOwGfGsQ7niiI6rYVJ+y97l1VTrrnmYCR98BBEETO4T2GeSISEcyAHcbNYG05W0DXYO8ry710to9Bd/wO6TPCle91OT/2Fq57/wAprw0cYA2DLysuTdMO8idgfA0cDCPFBFwGdDy57mHBXNLJ0fDfhuHJ6dFqYfKvdTB0U54sBKicCmjDs7Vr7+sOthjnnRpbQJudnRSXJ9OLr/utegDbgRZYMW3WtEYWc7F03p/v41OP4ysSax0mW/1ItTA3J+d0vfVKD59+oliCUbquzmzWKB8sgJexd2/tRwGI1jWDHaza8Ls9bJnsaXJNQg2uBzH5yuerhcn7DXt5YlTXc3C1sdFcGy1ct+MK1Y8tiutYt
  ...
}

Outro

What encouraged me to write this was an experimental project I am still developing this week. It’s called Pixel City and the artwork is very simple but generated totally on-chain.

You can take a look at it on my simple website:

Subscribe to naomsa
Receive the latest updates directly to your inbox.
Verification
This entry has been permanently stored onchain and signed by its creator.